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,534 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { Box, Text, render, useInput } from "ink";
4
- import InkSpinner from "ink-spinner";
5
- import path from "path";
6
- import { fetchModelsLive, getActiveProvider, getConfigDir, loadConfig, normalizeOllamaBaseURL, saveConfig, } from "../../config.js";
7
- import { theme } from "../../tui/theme.js";
8
- import { detectLocalOllama } from "./detect.js";
9
- import { applyAddProvider, applyRemoveProvider, applySwitchProvider, applyUpdateProvider, filterChoices, maskApiKey, suggestName, validateProviderName, } from "./provider-form.js";
10
- const OPENAI_URL = "https://api.openai.com/v1";
11
- const OLLAMA_URL = "http://localhost:11434/v1";
12
- export async function renderSetupWizard(mode) {
13
- return new Promise((resolve) => {
14
- const instance = render(_jsx(SetupApp, { mode: mode, onDone: (result) => {
15
- instance.unmount();
16
- resolve(result);
17
- } }), { exitOnCtrlC: false });
18
- });
19
- }
20
- function SetupApp({ mode, onDone }) {
21
- const [screen, setScreen] = useState(() => mode === "session-gate" ? { id: "gate" } : mode === "hub" ? { id: "hub" } : { id: "type" });
22
- const [draft, setDraft] = useState(null);
23
- const [detected, setDetected] = useState({ found: false });
24
- const [error, setError] = useState("");
25
- const [tick, setTick] = useState(0);
26
- const reload = () => setTick((n) => n + 1);
27
- void tick;
28
- const config = loadConfig();
29
- const abortFirstRun = () => onDone("aborted");
30
- const finishHub = () => onDone("saved");
31
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { bold: true, color: theme.accent, children: "\u914D\u7F6E\u670D\u52A1\u5546" }), screen.id === "gate" && (_jsx(ChoiceList, { items: [
32
- { label: "开始配置", value: "start" },
33
- { label: "退出", value: "exit" },
34
- ], onSelect: (value) => {
35
- if (value === "exit")
36
- onDone("aborted");
37
- else
38
- setScreen({ id: "type" });
39
- }, onCancel: () => onDone("aborted") })), screen.id === "hub" && (_jsx(HubScreen, { config: config, onAdd: () => {
40
- setDraft({
41
- fromHub: true,
42
- type: "openai-compatible",
43
- baseURL: OPENAI_URL,
44
- apiKey: "",
45
- defaultModel: "",
46
- name: "",
47
- });
48
- setError("");
49
- setScreen({ id: "type" });
50
- }, onPick: (action) => {
51
- const providers = config.providers ?? [];
52
- if (action === "switch" && providers.length <= 1) {
53
- setScreen({ id: "message", text: "当前只有一个服务商,无需切换。", back: "hub" });
54
- return;
55
- }
56
- setScreen({ id: "pick", action });
57
- }, onDone: finishHub })), screen.id === "pick" && (_jsx(ProviderPick, { config: config, onCancel: () => setScreen({ id: "hub" }), onSelect: (provider) => {
58
- const name = provider.name ?? "";
59
- if (screen.action === "switch") {
60
- const next = applySwitchProvider(config, name);
61
- if (isFormError(next)) {
62
- setScreen({ id: "message", text: next.error, back: "hub" });
63
- return;
64
- }
65
- saveConfig(next);
66
- logSaved();
67
- reload();
68
- setScreen({ id: "hub" });
69
- return;
70
- }
71
- if (screen.action === "delete") {
72
- setScreen({ id: "delete-confirm", name });
73
- return;
74
- }
75
- if (screen.action === "test") {
76
- void (async () => {
77
- const live = await fetchModelsLive(provider.baseURL, provider.apiKey);
78
- if (live.ok && live.models.length > 0) {
79
- setScreen({
80
- id: "message",
81
- text: `可访问,${live.models.length} 个模型`,
82
- back: "hub",
83
- });
84
- return;
85
- }
86
- const status = live.status != null ? `HTTP ${live.status}。` : "";
87
- const hint = provider.type === "ollama" ? "请确认 Ollama 已启动。" : "请检查 API 地址和密钥。";
88
- setScreen({
89
- id: "message",
90
- text: redactSecrets(`无法连接到该服务商。${status}${hint}`),
91
- back: "hub",
92
- });
93
- })();
94
- return;
95
- }
96
- setDraft(draftFromProvider(provider, true));
97
- setError("");
98
- setScreen({ id: "type" });
99
- } })), screen.id === "delete-confirm" && (_jsx(ChoiceList, { title: `确定删除服务商 "${screen.name}"?`, items: [
100
- { label: "删除", value: "yes" },
101
- { label: "取消", value: "no" },
102
- ], onSelect: (value) => {
103
- if (value !== "yes") {
104
- setScreen({ id: "hub" });
105
- return;
106
- }
107
- const next = applyRemoveProvider(loadConfig(), screen.name);
108
- if (isFormError(next)) {
109
- setScreen({ id: "message", text: next.error, back: "hub" });
110
- return;
111
- }
112
- saveConfig(next);
113
- logSaved();
114
- reload();
115
- setScreen({ id: "hub" });
116
- }, onCancel: () => setScreen({ id: "hub" }) })), screen.id === "message" && (_jsx(MessageScreen, { text: screen.text, onClose: () => {
117
- if (screen.back === "exit-saved")
118
- finishHub();
119
- else
120
- setScreen({ id: "hub" });
121
- } })), screen.id === "type" && (_jsx(TypeScreen, { detected: detected, onDetected: setDetected, initial: draft?.type, onCancel: () => {
122
- if (draft?.fromHub || mode === "hub")
123
- setScreen({ id: "hub" });
124
- else
125
- abortFirstRun();
126
- }, onSelect: (type) => {
127
- const next = startDraft(type, draft, detected);
128
- setDraft(next);
129
- setError("");
130
- if (type === "openai")
131
- setScreen({ id: "key" });
132
- else
133
- setScreen({ id: "url" });
134
- } })), screen.id === "url" && draft && (_jsx(FieldScreen, { label: draft.type === "ollama" ? "Ollama 地址" : "API 地址", value: draft.baseURL, error: error, onChange: (baseURL) => setDraft({ ...draft, baseURL }), onCancel: () => {
135
- setError("");
136
- setScreen({ id: "type" });
137
- }, onSubmit: () => {
138
- const trimmed = draft.baseURL.trim();
139
- if (!trimmed) {
140
- setError("API 地址不能为空。");
141
- return;
142
- }
143
- const baseURL = draft.type === "ollama" ? normalizeOllamaBaseURL(trimmed) : trimmed.replace(/\/$/, "");
144
- setDraft({ ...draft, baseURL });
145
- setError("");
146
- if (draft.type === "ollama")
147
- setScreen({ id: "fetch" });
148
- else
149
- setScreen({ id: "key" });
150
- } })), screen.id === "key" && draft && (_jsx(FieldScreen, { label: "API \u5BC6\u94A5", value: draft.apiKey, mask: true, placeholder: draft.existingKey ? `已保存 (${maskApiKey(draft.existingKey)}),回车保留` : undefined, error: error, onChange: (apiKey) => setDraft({ ...draft, apiKey }), onCancel: () => {
151
- setError("");
152
- if (draft.type === "openai")
153
- setScreen({ id: "type" });
154
- else
155
- setScreen({ id: "url" });
156
- }, onSubmit: () => {
157
- if (!draft.apiKey.trim() && draft.existingKey) {
158
- setDraft({ ...draft, apiKey: draft.existingKey });
159
- setError("");
160
- setScreen({ id: "fetch" });
161
- return;
162
- }
163
- if (!draft.apiKey.trim()) {
164
- setError("API 密钥不能为空。");
165
- return;
166
- }
167
- setError("");
168
- setScreen({ id: "fetch" });
169
- } })), screen.id === "fetch" && draft && (_jsx(FetchScreen, { draft: draft, onBack: () => setScreen(draft.type === "ollama" ? { id: "url" } : { id: "key" }), onList: (models) => {
170
- setDraft({ ...draft, models });
171
- setError("");
172
- setScreen({ id: "models", models });
173
- }, onEmpty: () => {
174
- setError("");
175
- setScreen({ id: "model-manual" });
176
- } })), screen.id === "models" && draft && (_jsx(ModelListScreen, { models: screen.models, current: draft.defaultModel, onCancel: () => setScreen(draft.type === "openai" ? { id: "key" } : { id: "url" }), onSelect: (defaultModel) => {
177
- setDraft({ ...draft, defaultModel });
178
- setError("");
179
- setScreen({ id: "name" });
180
- } })), screen.id === "model-manual" && draft && (_jsx(FieldScreen, { label: "\u65E0\u6CD5\u83B7\u53D6\u6A21\u578B\u5217\u8868\uFF0C\u8BF7\u624B\u52A8\u8F93\u5165\u9ED8\u8BA4\u6A21\u578B", value: draft.defaultModel, error: error, onChange: (defaultModel) => setDraft({ ...draft, defaultModel }), onCancel: () => setScreen(draft.type === "openai" ? { id: "key" } : { id: "url" }), onSubmit: () => {
181
- if (!draft.defaultModel.trim()) {
182
- setError("默认模型不能为空。");
183
- return;
184
- }
185
- setError("");
186
- if (draft.type === "openai-compatible")
187
- setScreen({ id: "context" });
188
- else
189
- setScreen({ id: "name" });
190
- } })), screen.id === "context" && draft && (_jsx(FieldScreen, { label: "\u4E0A\u4E0B\u6587\u7A97\u53E3\uFF08token\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09", value: draft.contextWindow != null ? String(draft.contextWindow) : "", error: error, onChange: (raw) => {
191
- const trimmed = raw.trim();
192
- if (!trimmed) {
193
- setDraft({ ...draft, contextWindow: undefined });
194
- return;
195
- }
196
- const n = Number.parseInt(trimmed, 10);
197
- setDraft({ ...draft, contextWindow: Number.isFinite(n) ? n : undefined });
198
- }, onCancel: () => setScreen({ id: "model-manual" }), onSubmit: (raw) => {
199
- const trimmed = raw.trim();
200
- if (!trimmed) {
201
- setDraft({ ...draft, contextWindow: undefined });
202
- setError("");
203
- setScreen({ id: "name" });
204
- return;
205
- }
206
- const n = Number.parseInt(trimmed, 10);
207
- if (!Number.isFinite(n) || n <= 0) {
208
- setError("上下文窗口必须是大于 0 的整数。");
209
- return;
210
- }
211
- setDraft({ ...draft, contextWindow: n });
212
- setError("");
213
- setScreen({ id: "name" });
214
- } })), screen.id === "name" && draft && (_jsx(FieldScreen, { label: "\u540D\u79F0", value: draft.name, error: error, onChange: (name) => setDraft({ ...draft, name }), onCancel: () => {
215
- if (draft.models && draft.models.length > 0)
216
- setScreen({ id: "models", models: draft.models });
217
- else
218
- setScreen({ id: "model-manual" });
219
- }, onSubmit: () => {
220
- const nameError = validateProviderName(draft.name);
221
- if (nameError) {
222
- setError(nameError);
223
- return;
224
- }
225
- setError("");
226
- setScreen({ id: "summary" });
227
- } })), screen.id === "summary" && draft && (_jsx(SummaryScreen, { draft: draft, error: error, onCancel: () => setScreen({ id: "name" }), onConfirm: () => {
228
- const result = commitDraft(draft);
229
- if (isFormError(result)) {
230
- setError(result.error);
231
- setScreen({ id: "name" });
232
- return;
233
- }
234
- saveConfig(result);
235
- logSaved();
236
- if (draft.fromHub || mode === "hub") {
237
- reload();
238
- setDraft(null);
239
- setScreen({ id: "hub" });
240
- return;
241
- }
242
- onDone("saved");
243
- } }))] }));
244
- }
245
- function HubScreen({ config, onAdd, onPick, onDone, }) {
246
- const providers = config.providers ?? [];
247
- const items = [
248
- { label: "添加", value: "add" },
249
- { label: "设为当前", value: "switch" },
250
- { label: "修改", value: "edit" },
251
- { label: "删除", value: "delete" },
252
- { label: "测试连接", value: "test" },
253
- { label: "完成", value: "done" },
254
- ];
255
- return (_jsxs(Box, { flexDirection: "column", children: [providers.map((p) => (_jsxs(Text, { color: p.name === config.activeProvider ? theme.accent : theme.muted, children: [p.name === config.activeProvider ? "* " : " ", p.name ?? "(未命名)", " ", p.baseURL, " ", p.defaultModel ?? "未设置模型"] }, p.name ?? p.baseURL))), _jsx(ChoiceList, { items: items, onSelect: (value) => {
256
- if (value === "add")
257
- onAdd();
258
- else if (value === "done")
259
- onDone();
260
- else
261
- onPick(value);
262
- }, onCancel: onDone })] }));
263
- }
264
- function ProviderPick({ config, onSelect, onCancel, }) {
265
- const providers = config.providers ?? [];
266
- const active = getActiveProvider(config)?.name;
267
- return (_jsx(ChoiceList, { items: providers.map((p) => ({
268
- label: `${p.name ?? "(未命名)"}${p.name === active ? " *" : ""} ${p.baseURL}`,
269
- value: p.name ?? "",
270
- })), initial: Math.max(0, providers.findIndex((p) => p.name === active)), onSelect: (name) => {
271
- const provider = providers.find((p) => p.name === name);
272
- if (provider)
273
- onSelect(provider);
274
- }, onCancel: onCancel }));
275
- }
276
- function TypeScreen({ detected, onDetected, initial, onSelect, onCancel, }) {
277
- useEffect(() => {
278
- let cancelled = false;
279
- void detectLocalOllama().then((result) => {
280
- if (!cancelled)
281
- onDetected(result);
282
- });
283
- return () => {
284
- cancelled = true;
285
- };
286
- }, [onDetected]);
287
- const items = [
288
- { label: "OpenAI 兼容接口", hint: "OpenRouter、DeepSeek、Groq 等", value: "openai-compatible" },
289
- { label: "OpenAI", hint: "官方接口", value: "openai" },
290
- {
291
- label: "Ollama(本地)",
292
- hint: detected.found ? "已检测到" : "本机模型",
293
- value: "ollama",
294
- },
295
- ];
296
- const initialIndex = initial === "openai" ? 1 : initial === "ollama" ? 2 : detected.found && !initial ? 2 : 0;
297
- return (_jsx(ChoiceList, { items: items, initial: initialIndex, onSelect: (value) => onSelect(value), onCancel: onCancel }, `${initial ?? "auto"}-${detected.found ? "detected" : "plain"}`));
298
- }
299
- function FetchScreen({ draft, onList, onEmpty, onBack, }) {
300
- const onListRef = useRef(onList);
301
- const onEmptyRef = useRef(onEmpty);
302
- onListRef.current = onList;
303
- onEmptyRef.current = onEmpty;
304
- useInput((_input, key) => {
305
- if (key.escape)
306
- onBack();
307
- });
308
- useEffect(() => {
309
- let cancelled = false;
310
- void fetchModelsLive(draft.baseURL, draft.apiKey || "ollama").then((live) => {
311
- if (cancelled)
312
- return;
313
- if (live.ok && live.models.length > 0)
314
- onListRef.current(live.models);
315
- else
316
- onEmptyRef.current();
317
- });
318
- return () => {
319
- cancelled = true;
320
- };
321
- }, [draft.apiKey, draft.baseURL]);
322
- return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: _jsx(InkSpinner, { type: "dots" }) }), _jsx(Text, { color: theme.muted, children: " \u6B63\u5728\u83B7\u53D6\u6A21\u578B\u5217\u8868\u2026" })] }));
323
- }
324
- function ModelListScreen({ models, current, onSelect, onCancel, }) {
325
- const [query, setQuery] = useState("");
326
- const [index, setIndex] = useState(0);
327
- const filtered = filterChoices(query, models);
328
- const q = query.trim();
329
- useArmEnter((input, key, isEnter) => {
330
- if (key.escape) {
331
- onCancel();
332
- return;
333
- }
334
- if (key.downArrow && filtered.length > 0) {
335
- setIndex((i) => (i + 1) % filtered.length);
336
- return;
337
- }
338
- if (key.upArrow && filtered.length > 0) {
339
- setIndex((i) => (i - 1 + filtered.length) % filtered.length);
340
- return;
341
- }
342
- if (isEnter) {
343
- if (filtered.length > 0)
344
- onSelect(filtered[Math.min(index, filtered.length - 1)]);
345
- else if (q)
346
- onSelect(q);
347
- return;
348
- }
349
- if (key.backspace || key.delete) {
350
- setQuery((v) => Array.from(v).slice(0, -1).join(""));
351
- setIndex(0);
352
- return;
353
- }
354
- if (key.ctrl || key.meta || key.tab)
355
- return;
356
- const clean = input.replace(/[\r\n]+$/, "");
357
- if (clean) {
358
- setQuery((v) => v + clean);
359
- setIndex(0);
360
- }
361
- });
362
- const shownIndex = Math.min(index, Math.max(0, filtered.length - 1));
363
- const windowSize = 12;
364
- const windowStart = Math.min(shownIndex, Math.max(0, filtered.length - windowSize));
365
- const shown = filtered.slice(windowStart, windowStart + windowSize);
366
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.muted, children: "\u8F93\u5165\u53EF\u7B5B\u9009" }), _jsx(Text, { color: query ? theme.accent : theme.muted, children: query || " " }), shown.map((model, i) => {
367
- const selected = windowStart + i === shownIndex;
368
- return (_jsxs(Text, { color: selected ? theme.accent : undefined, bold: selected, children: [selected ? "▸ " : " ", model, model === current ? " ← 当前" : ""] }, model));
369
- }), filtered.length === 0 && q !== "" && _jsxs(Text, { color: theme.accent, children: ["\u25B8 \u4F7F\u7528\u81EA\u5B9A\u4E49\u6A21\u578B: ", q] }), _jsx(Text, { color: theme.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
370
- }
371
- function FieldScreen({ label, value, mask, placeholder, error, onChange, onSubmit, onCancel, }) {
372
- useArmEnter((input, key, isEnter) => {
373
- if (key.escape) {
374
- onCancel();
375
- return;
376
- }
377
- if (isEnter) {
378
- onSubmit(value);
379
- return;
380
- }
381
- if (key.backspace || key.delete) {
382
- onChange(Array.from(value).slice(0, -1).join(""));
383
- return;
384
- }
385
- if (key.ctrl || key.meta || key.tab)
386
- return;
387
- const clean = input.replace(/[\r\n]+$/, "");
388
- if (clean)
389
- onChange(value + clean);
390
- });
391
- const shown = mask ? "*".repeat(Array.from(value).length) : value;
392
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: label }), _jsx(Text, { color: theme.accent, children: shown || placeholder || " " }), error ? _jsx(Text, { color: theme.error, children: error }) : null, _jsx(Text, { color: theme.muted, children: "Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
393
- }
394
- function SummaryScreen({ draft, error, onConfirm, onCancel, }) {
395
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["\u7C7B\u578B ", typeLabel(draft.type)] }), _jsxs(Text, { children: ["\u5730\u5740 ", draft.baseURL] }), _jsxs(Text, { children: ["\u5BC6\u94A5 ", maskApiKey(draft.apiKey)] }), _jsxs(Text, { children: ["\u6A21\u578B ", draft.defaultModel] }), _jsxs(Text, { children: ["\u540D\u79F0 ", draft.name.trim()] }), error ? _jsx(Text, { color: theme.error, children: error }) : null, _jsx(ChoiceList, { items: [
396
- { label: "保存", value: "save" },
397
- { label: "返回", value: "back" },
398
- ], onSelect: (value) => {
399
- if (value === "save")
400
- onConfirm();
401
- else
402
- onCancel();
403
- }, onCancel: onCancel })] }));
404
- }
405
- function MessageScreen({ text, onClose }) {
406
- useArmEnter((_input, key, isEnter) => {
407
- if (key.escape || isEnter)
408
- onClose();
409
- });
410
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: text }), _jsx(Text, { color: theme.muted, children: "Enter / Esc \u7EE7\u7EED" })] }));
411
- }
412
- function ChoiceList({ title, items, initial = 0, onSelect, onCancel, }) {
413
- const [index, setIndex] = useState(Math.min(Math.max(0, initial), Math.max(0, items.length - 1)));
414
- useArmEnter((_input, key, isEnter) => {
415
- if (key.escape) {
416
- onCancel();
417
- return;
418
- }
419
- if (items.length === 0)
420
- return;
421
- if (key.downArrow) {
422
- setIndex((i) => (i + 1) % items.length);
423
- return;
424
- }
425
- if (key.upArrow) {
426
- setIndex((i) => (i - 1 + items.length) % items.length);
427
- return;
428
- }
429
- if (isEnter)
430
- onSelect(items[index].value);
431
- });
432
- return (_jsxs(Box, { flexDirection: "column", children: [title ? _jsx(Text, { children: title }) : null, items.map((item, i) => (_jsxs(Text, { color: i === index ? theme.accent : undefined, bold: i === index, children: [i === index ? "▸ " : " ", item.label, item.hint ? _jsxs(Text, { color: theme.muted, children: [" ", item.hint] }) : null] }, item.value))), _jsx(Text, { color: theme.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
433
- }
434
- function useArmEnter(handler) {
435
- const armed = useRef(false);
436
- const handlerRef = useRef(handler);
437
- handlerRef.current = handler;
438
- useEffect(() => {
439
- const timer = setTimeout(() => {
440
- armed.current = true;
441
- }, 0);
442
- return () => clearTimeout(timer);
443
- }, []);
444
- useInput((input, key) => {
445
- const isEnter = key.return || /[\r\n]+$/.test(input);
446
- if (isEnter && !armed.current)
447
- return;
448
- handlerRef.current(input, key, isEnter);
449
- });
450
- }
451
- function startDraft(type, previous, detected) {
452
- const fromHub = previous?.fromHub === true || previous?.editingName != null;
453
- const editingName = previous?.editingName;
454
- if (type === "openai") {
455
- return {
456
- fromHub,
457
- editingName,
458
- type,
459
- baseURL: OPENAI_URL,
460
- apiKey: "",
461
- existingKey: previous?.type === "openai" ? (previous.existingKey ?? previous.apiKey) : previous?.existingKey,
462
- defaultModel: previous?.type === "openai" ? previous.defaultModel : "",
463
- name: previous?.name ?? "",
464
- };
465
- }
466
- if (type === "ollama") {
467
- return {
468
- fromHub,
469
- editingName,
470
- type,
471
- baseURL: detected.baseURL || previous?.baseURL || OLLAMA_URL,
472
- apiKey: "ollama",
473
- defaultModel: previous?.type === "ollama" ? previous.defaultModel : "llama3",
474
- name: previous?.name ?? "",
475
- };
476
- }
477
- return {
478
- fromHub,
479
- editingName,
480
- type,
481
- baseURL: previous?.type === "openai-compatible" ? previous.baseURL : OPENAI_URL,
482
- apiKey: "",
483
- existingKey: previous?.existingKey,
484
- defaultModel: previous?.defaultModel ?? "",
485
- name: previous?.name ?? "",
486
- };
487
- }
488
- function draftFromProvider(provider, fromHub) {
489
- const type = provider.type ?? "openai-compatible";
490
- return {
491
- fromHub,
492
- editingName: provider.name,
493
- type,
494
- baseURL: provider.baseURL,
495
- apiKey: "",
496
- existingKey: provider.apiKey,
497
- defaultModel: provider.defaultModel ?? "",
498
- contextWindow: provider.contextWindow,
499
- name: provider.name ?? "",
500
- };
501
- }
502
- function commitDraft(draft) {
503
- const config = loadConfig();
504
- const existing = (config.providers ?? []).map((p) => p.name).filter((name) => Boolean(name));
505
- const name = draft.name.trim() ||
506
- suggestName(draft.type, draft.baseURL, existing.filter((item) => item !== draft.editingName));
507
- const payload = {
508
- name,
509
- type: draft.type,
510
- baseURL: draft.baseURL,
511
- apiKey: draft.apiKey || draft.existingKey || (draft.type === "ollama" ? "ollama" : ""),
512
- defaultModel: draft.defaultModel.trim(),
513
- ...(draft.contextWindow != null ? { contextWindow: draft.contextWindow } : {}),
514
- };
515
- if (draft.editingName)
516
- return applyUpdateProvider(config, draft.editingName, payload);
517
- return applyAddProvider(config, payload, { overwrite: false });
518
- }
519
- function typeLabel(type) {
520
- if (type === "openai")
521
- return "OpenAI";
522
- if (type === "ollama")
523
- return "Ollama";
524
- return "OpenAI 兼容接口";
525
- }
526
- function logSaved() {
527
- console.log(`已保存到 ${path.join(getConfigDir(), "config.json")}`);
528
- }
529
- function redactSecrets(text) {
530
- return text.replace(/Bearer\s+\S+/gi, "Bearer ****").replace(/\bsk-[A-Za-z0-9_-]+/g, "sk-****");
531
- }
532
- function isFormError(result) {
533
- return Object.keys(result).length === 1 && "error" in result;
534
- }
package/dist/clipboard.js DELETED
@@ -1,142 +0,0 @@
1
- import { execSync } from "child_process";
2
- import { readFileSync, unlinkSync } from "fs";
3
- import path from "path";
4
- import os from "os";
5
- function readTmpImage(tmpFile) {
6
- const data = readFileSync(tmpFile);
7
- try {
8
- unlinkSync(tmpFile);
9
- }
10
- catch { }
11
- return data.length > 0 ? data : null;
12
- }
13
- function tmpImagePath() {
14
- return path.join(os.tmpdir(), `min-agent-paste-${Date.now()}.png`);
15
- }
16
- export function getClipboardImage() {
17
- switch (process.platform) {
18
- case "darwin":
19
- return getClipboardImageMac();
20
- case "linux":
21
- return getClipboardImageLinux();
22
- case "win32":
23
- return getClipboardImageWindows();
24
- default:
25
- return null;
26
- }
27
- }
28
- /** Write plain text to the system clipboard. False on failure (silent). */
29
- export function writeClipboard(text) {
30
- switch (process.platform) {
31
- case "darwin":
32
- try {
33
- execSync("pbcopy", { input: text, stdio: "pipe" });
34
- return true;
35
- }
36
- catch {
37
- return false;
38
- }
39
- case "linux": {
40
- try {
41
- execSync("xclip -selection clipboard", { input: text, stdio: "pipe" });
42
- return true;
43
- }
44
- catch { }
45
- try {
46
- execSync("xsel --clipboard --input", { input: text, stdio: "pipe" });
47
- return true;
48
- }
49
- catch { }
50
- return false;
51
- }
52
- case "win32":
53
- try {
54
- execSync('powershell -NoProfile -Command "Set-Clipboard -Value ([Console]::In.ReadToEnd())"', {
55
- input: text,
56
- stdio: "pipe",
57
- });
58
- return true;
59
- }
60
- catch {
61
- return false;
62
- }
63
- default:
64
- return false;
65
- }
66
- }
67
- function getClipboardImageMac() {
68
- const tmpFile = tmpImagePath();
69
- try {
70
- // Try pngpaste first (brew install pngpaste)
71
- execSync(`pngpaste "${tmpFile}" 2>/dev/null`, { stdio: "pipe" });
72
- const data = readTmpImage(tmpFile);
73
- if (data)
74
- return { data, mimeType: "image/png" };
75
- }
76
- catch { }
77
- try {
78
- // Fallback: osascript to save clipboard image
79
- const script = `
80
- set tmpFile to POSIX file "${tmpFile}"
81
- try
82
- set imgData to the clipboard as «class PNGf»
83
- set fp to open for access tmpFile with write permission
84
- write imgData to fp
85
- close access fp
86
- return "ok"
87
- on error
88
- return "no_image"
89
- end try
90
- `;
91
- const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { encoding: "utf-8" }).trim();
92
- if (result === "ok") {
93
- const data = readTmpImage(tmpFile);
94
- if (data)
95
- return { data, mimeType: "image/png" };
96
- }
97
- }
98
- catch { }
99
- return null;
100
- }
101
- function getClipboardImageLinux() {
102
- try {
103
- // xclip
104
- const data = execSync("xclip -selection clipboard -t image/png -o 2>/dev/null", {
105
- maxBuffer: 10 * 1024 * 1024,
106
- });
107
- if (data.length > 0)
108
- return { data, mimeType: "image/png" };
109
- }
110
- catch { }
111
- try {
112
- // xsel fallback
113
- const data = execSync("xsel --clipboard --output 2>/dev/null", {
114
- maxBuffer: 10 * 1024 * 1024,
115
- });
116
- // Check if it's actually image data (PNG magic bytes)
117
- if (data.length > 8 && data[0] === 0x89 && data[1] === 0x50) {
118
- return { data, mimeType: "image/png" };
119
- }
120
- }
121
- catch { }
122
- return null;
123
- }
124
- function getClipboardImageWindows() {
125
- const tmpFile = tmpImagePath();
126
- try {
127
- const ps = `
128
- Add-Type -AssemblyName System.Windows.Forms
129
- $img = [System.Windows.Forms.Clipboard]::GetImage()
130
- if ($img) { $img.Save('${tmpFile.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' }
131
- else { Write-Output 'no_image' }
132
- `;
133
- const result = execSync(`powershell -NoProfile -Command "${ps}"`, { encoding: "utf-8" }).trim();
134
- if (result === "ok") {
135
- const data = readTmpImage(tmpFile);
136
- if (data)
137
- return { data, mimeType: "image/png" };
138
- }
139
- }
140
- catch { }
141
- return null;
142
- }