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,268 +0,0 @@
1
- import { fetchModels, getActiveProvider, getEffectiveConfig, getBudgetSnapshot, setBudgetMaxCost, setSandboxConfig, setPermissionMode, parsePermissionMode, parseThinkingEffort, } from "../config.js";
2
- import { setThinkingEffort } from "../thinking.js";
3
- import { parseNetworkPolicy, parseSandboxMode } from "../sandbox.js";
4
- import { readScope } from "../scope.js";
5
- import { getWorkingTreeDiff } from "../tui/diff-view.js";
6
- import { scanProject } from "../code-mode.js";
7
- import { getContextWindowInfo } from "../context-window.js";
8
- import { parseCtxChoice, setOllamaContextChoice, ctxPayload, CTX_CHOICES } from "../ctx.js";
9
- import { getModelPrice, estimateCost } from "../pricing.js";
10
- import { checkForUpdate } from "../updater.js";
11
- import { sendJson, sendJsonError, readJsonBody, readStringPathList, sandboxPayload, permissionPayload, thinkingPayload, memoryPayload, } from "./common.js";
12
- export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
13
- if (req.method === "GET" && pathname === "/health") {
14
- sendJson(res, 200, { ok: true, service: "min-agent", version: ctx.version });
15
- return true;
16
- }
17
- if (req.method === "GET" && pathname === "/v1/meta") {
18
- sendJson(res, 200, {
19
- version: ctx.version,
20
- cwd: process.cwd(),
21
- instructions_chars: ctx.instructions.join("\n").length,
22
- sandbox: sandboxPayload(),
23
- permission: permissionPayload(),
24
- thinking: thinkingPayload(),
25
- memory: memoryPayload(),
26
- });
27
- return true;
28
- }
29
- if (req.method === "GET" && pathname === "/v1/update") {
30
- const check = await checkForUpdate({ currentVersion: ctx.version });
31
- sendJson(res, 200, {
32
- current: check.current,
33
- latest: check.latest,
34
- update_available: check.updateAvailable,
35
- ...(check.latest === null ? { error: "registry_unavailable" } : {}),
36
- });
37
- return true;
38
- }
39
- if (req.method === "GET" && pathname === "/v1/models") {
40
- const config = getEffectiveConfig();
41
- const provider = getActiveProvider(config);
42
- const base = provider?.baseURL;
43
- const key = provider?.apiKey;
44
- if (!base || !key) {
45
- sendJson(res, 500, { error: "provider_not_configured" });
46
- return true;
47
- }
48
- const models = await fetchModels(base, key);
49
- sendJson(res, 200, {
50
- default_model: provider?.defaultModel ?? null,
51
- models,
52
- });
53
- return true;
54
- }
55
- if (req.method === "GET" && pathname === "/v1/budget") {
56
- const snap = getBudgetSnapshot();
57
- sendJson(res, 200, { maxCostUSD: snap.maxCostUSD ?? null, source: snap.source });
58
- return true;
59
- }
60
- if (req.method === "POST" && pathname === "/v1/budget") {
61
- const parsed = await readJsonBody(req);
62
- if (!parsed.ok) {
63
- sendJsonError(res, parsed);
64
- return true;
65
- }
66
- const n = parsed.value.maxCostUSD;
67
- if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) {
68
- sendJson(res, 400, { error: "invalid_max_cost", detail: "maxCostUSD must be a positive number" });
69
- return true;
70
- }
71
- const scopeParsed = readScope(parsed.value.scope);
72
- if (!scopeParsed.ok) {
73
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
74
- return true;
75
- }
76
- const scope = scopeParsed.scope ?? "global";
77
- setBudgetMaxCost(n, scope);
78
- const snap = getBudgetSnapshot();
79
- sendJson(res, 200, { ok: true, maxCostUSD: snap.maxCostUSD ?? n, source: snap.source, scope });
80
- return true;
81
- }
82
- if (req.method === "GET" && pathname === "/v1/sandbox") {
83
- sendJson(res, 200, sandboxPayload());
84
- return true;
85
- }
86
- if (req.method === "GET" && pathname === "/v1/permission") {
87
- sendJson(res, 200, permissionPayload());
88
- return true;
89
- }
90
- if (req.method === "POST" && pathname === "/v1/permission") {
91
- const parsed = await readJsonBody(req);
92
- if (!parsed.ok) {
93
- sendJsonError(res, parsed);
94
- return true;
95
- }
96
- const mode = parsePermissionMode(parsed.value.permission);
97
- if (!mode) {
98
- sendJson(res, 400, { error: "invalid_permission", detail: "permission must be ask, accept-edits, or allow-all" });
99
- return true;
100
- }
101
- const scopeParsed = readScope(parsed.value.scope);
102
- if (!scopeParsed.ok) {
103
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
104
- return true;
105
- }
106
- const scope = scopeParsed.scope ?? "global";
107
- setPermissionMode(mode, scope);
108
- sendJson(res, 200, { ok: true, scope, ...permissionPayload() });
109
- return true;
110
- }
111
- if (req.method === "GET" && pathname === "/v1/thinking") {
112
- sendJson(res, 200, thinkingPayload());
113
- return true;
114
- }
115
- if (req.method === "POST" && pathname === "/v1/thinking") {
116
- const parsed = await readJsonBody(req);
117
- if (!parsed.ok) {
118
- sendJsonError(res, parsed);
119
- return true;
120
- }
121
- const effort = parseThinkingEffort(parsed.value.thinking);
122
- if (!effort) {
123
- sendJson(res, 400, { error: "invalid_thinking", detail: "thinking must be off, low, medium, high, or max" });
124
- return true;
125
- }
126
- const scopeParsed = readScope(parsed.value.scope);
127
- if (!scopeParsed.ok) {
128
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
129
- return true;
130
- }
131
- const scope = scopeParsed.scope ?? "global";
132
- setThinkingEffort(effort, scope);
133
- sendJson(res, 200, { ok: true, scope, ...thinkingPayload() });
134
- return true;
135
- }
136
- if (req.method === "POST" && pathname === "/v1/sandbox") {
137
- const parsed = await readJsonBody(req);
138
- if (!parsed.ok) {
139
- sendJsonError(res, parsed);
140
- return true;
141
- }
142
- const body = parsed.value;
143
- const mode = body.mode === undefined ? undefined : parseSandboxMode(body.mode);
144
- if (body.mode !== undefined && !mode) {
145
- sendJson(res, 400, { error: "invalid_sandbox", detail: "mode must be off, workspace, or strict" });
146
- return true;
147
- }
148
- const network = body.network === undefined ? undefined : parseNetworkPolicy(body.network);
149
- if (body.network !== undefined && !network) {
150
- sendJson(res, 400, { error: "invalid_network", detail: "network must be allow or deny" });
151
- return true;
152
- }
153
- const extraWrite = readStringPathList(body.extraWriteRoots);
154
- const extraRead = readStringPathList(body.extraReadRoots);
155
- if (!extraWrite.ok || !extraRead.ok) {
156
- sendJson(res, 400, {
157
- error: "invalid_roots",
158
- detail: "extraWriteRoots and extraReadRoots must be arrays of non-empty strings",
159
- });
160
- return true;
161
- }
162
- if (!mode && !network && extraWrite.value === undefined && extraRead.value === undefined) {
163
- sendJson(res, 400, { error: "invalid_body", detail: "Provide mode, network, extraWriteRoots, or extraReadRoots" });
164
- return true;
165
- }
166
- const scopeParsed = readScope(body.scope);
167
- if (!scopeParsed.ok) {
168
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
169
- return true;
170
- }
171
- const scope = scopeParsed.scope ?? "global";
172
- setSandboxConfig({
173
- ...(mode ? { mode } : {}),
174
- ...(network ? { network } : {}),
175
- ...(extraWrite.value ? { extraWriteRoots: extraWrite.value } : {}),
176
- ...(extraRead.value ? { extraReadRoots: extraRead.value } : {}),
177
- }, scope);
178
- sendJson(res, 200, { ok: true, scope, ...sandboxPayload() });
179
- return true;
180
- }
181
- if (req.method === "GET" && pathname === "/v1/diff") {
182
- const result = await getWorkingTreeDiff();
183
- if (!result.ok) {
184
- sendJson(res, 200, { ok: false, reason: result.reason, message: result.message });
185
- return true;
186
- }
187
- sendJson(res, 200, { ok: true, diff: result.diff });
188
- return true;
189
- }
190
- if (req.method === "GET" && pathname === "/v1/rules") {
191
- sendJson(res, 200, {
192
- count: ctx.instructions.length,
193
- rules: ctx.instructions.map((inst) => {
194
- const firstLine = inst.split("\n")[0] ?? "";
195
- return { source: firstLine.replace("Instructions from: ", ""), chars: inst.length };
196
- }),
197
- });
198
- return true;
199
- }
200
- if (req.method === "GET" && pathname === "/v1/project") {
201
- const project = scanProject();
202
- sendJson(res, 200, { project });
203
- return true;
204
- }
205
- if (req.method === "GET" && pathname === "/v1/context") {
206
- const config = getEffectiveConfig();
207
- const provider = getActiveProvider(config);
208
- const info = await getContextWindowInfo(provider?.defaultModel);
209
- sendJson(res, 200, {
210
- context_window: info.tokens,
211
- source: info.source,
212
- model: provider?.defaultModel ?? null,
213
- ...ctxPayload(),
214
- });
215
- return true;
216
- }
217
- if (req.method === "POST" && pathname === "/v1/context") {
218
- const parsed = await readJsonBody(req);
219
- if (!parsed.ok) {
220
- sendJsonError(res, parsed);
221
- return true;
222
- }
223
- const choice = typeof parsed.value.level === "string" ? parseCtxChoice(parsed.value.level) : null;
224
- if (!choice) {
225
- sendJson(res, 400, {
226
- error: "invalid_context_level",
227
- detail: `level must be one of: ${CTX_CHOICES.join(", ")}`,
228
- });
229
- return true;
230
- }
231
- const result = setOllamaContextChoice(choice);
232
- if (!result.ok) {
233
- sendJson(res, 400, {
234
- error: "ctx_not_supported",
235
- detail: "context levels are only available for an Ollama provider",
236
- });
237
- return true;
238
- }
239
- const provider = getActiveProvider(getEffectiveConfig());
240
- const info = await getContextWindowInfo(provider?.defaultModel);
241
- sendJson(res, 200, {
242
- ok: true,
243
- context_window: info.tokens,
244
- source: info.source,
245
- model: provider?.defaultModel ?? null,
246
- ...ctxPayload(),
247
- });
248
- return true;
249
- }
250
- if (req.method === "GET" && pathname === "/v1/cost") {
251
- const config = getEffectiveConfig();
252
- const provider = getActiveProvider(config);
253
- const modelId = url.searchParams.get("model") ?? provider?.defaultModel ?? null;
254
- const inputRaw = url.searchParams.get("input");
255
- const outputRaw = url.searchParams.get("output");
256
- const input = inputRaw ? parseInt(inputRaw, 10) : 0;
257
- const output = outputRaw ? parseInt(outputRaw, 10) : 0;
258
- if (Number.isNaN(input) || Number.isNaN(output) || input < 0 || output < 0) {
259
- sendJson(res, 400, { error: "invalid_tokens", detail: "input/output must be non-negative integers" });
260
- return true;
261
- }
262
- const price = modelId ? await getModelPrice(modelId) : null;
263
- const cost = price ? estimateCost({ inputTokens: input, outputTokens: output }, price) : null;
264
- sendJson(res, 200, { model: modelId, price, cost_usd: cost });
265
- return true;
266
- }
267
- return false;
268
- };
@@ -1,61 +0,0 @@
1
- import { listSessions, loadSession, deleteSession, renameSession } from "../sessions.js";
2
- import { sendJson, sendJsonError, readJsonBody, decodePathSegment } from "./common.js";
3
- export const handleSessionRoutes = async (req, res, _ctx, _url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/sessions") {
5
- const sessions = listSessions();
6
- sendJson(res, 200, { sessions });
7
- return true;
8
- }
9
- if (req.method === "DELETE" && pathname.startsWith("/v1/sessions/")) {
10
- const id = decodePathSegment(pathname.slice("/v1/sessions/".length));
11
- if (!id) {
12
- sendJson(res, 400, { error: "missing_session_id" });
13
- return true;
14
- }
15
- const ok = deleteSession(id);
16
- if (!ok) {
17
- sendJson(res, 404, { error: "session_not_found", session_id: id });
18
- return true;
19
- }
20
- sendJson(res, 200, { ok: true, deleted: id });
21
- return true;
22
- }
23
- if (req.method === "GET" && pathname.startsWith("/v1/sessions/")) {
24
- const id = decodePathSegment(pathname.slice("/v1/sessions/".length));
25
- if (!id) {
26
- sendJson(res, 400, { error: "missing_session_id" });
27
- return true;
28
- }
29
- const session = loadSession(id);
30
- if (!session) {
31
- sendJson(res, 404, { error: "session_not_found", session_id: id });
32
- return true;
33
- }
34
- sendJson(res, 200, { session });
35
- return true;
36
- }
37
- if (req.method === "POST" && pathname.startsWith("/v1/sessions/") && pathname.endsWith("/rename")) {
38
- const id = decodePathSegment(pathname.slice("/v1/sessions/".length, -"/rename".length));
39
- if (!id) {
40
- sendJson(res, 400, { error: "missing_session_id" });
41
- return true;
42
- }
43
- const parsed = await readJsonBody(req);
44
- if (!parsed.ok) {
45
- sendJsonError(res, parsed);
46
- return true;
47
- }
48
- const title = parsed.value.title?.trim();
49
- if (!title) {
50
- sendJson(res, 400, { error: "missing_title" });
51
- return true;
52
- }
53
- if (!renameSession(id, title)) {
54
- sendJson(res, 404, { error: "session_not_found", session_id: id });
55
- return true;
56
- }
57
- sendJson(res, 200, { ok: true, session_id: id, title });
58
- return true;
59
- }
60
- return false;
61
- };
@@ -1,70 +0,0 @@
1
- import { discoverSkills, getAllSkills, getSkill, getStaleDisabledSkills, setSkillEnabled } from "../skills.js";
2
- import { sendJson, readJsonBody, decodePathSegment, skillPayload } from "./common.js";
3
- export const handleSkillRoutes = async (req, res, _ctx, _url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/skills") {
5
- const all = getAllSkills();
6
- sendJson(res, 200, {
7
- count: all.length,
8
- enabled_count: all.filter((s) => s.enabled).length,
9
- stale_disabled: getStaleDisabledSkills(),
10
- skills: all.map(skillPayload),
11
- });
12
- return true;
13
- }
14
- if (req.method === "POST" && pathname === "/v1/skills/reload") {
15
- discoverSkills({ silent: true });
16
- const all = getAllSkills();
17
- sendJson(res, 200, {
18
- ok: true,
19
- count: all.length,
20
- enabled_count: all.filter((s) => s.enabled).length,
21
- stale_disabled: getStaleDisabledSkills(),
22
- skills: all.map(skillPayload),
23
- });
24
- return true;
25
- }
26
- const skillDetail = pathname.match(/^\/v1\/skills\/([^/]+)$/);
27
- if (req.method === "GET" && skillDetail) {
28
- const name = decodePathSegment(skillDetail[1]);
29
- if (!name) {
30
- sendJson(res, 400, { error: "invalid_skill_name" });
31
- return true;
32
- }
33
- const skill = getSkill(name);
34
- if (!skill) {
35
- sendJson(res, 404, { error: "skill_not_found", name, available: getAllSkills().map((s) => s.name) });
36
- return true;
37
- }
38
- sendJson(res, 200, { skill: { ...skillPayload(skill), content: skill.content } });
39
- return true;
40
- }
41
- const skillToggle = pathname.match(/^\/v1\/skills\/([^/]+)\/(enable|disable)$/);
42
- if (req.method === "POST" && skillToggle) {
43
- const name = decodePathSegment(skillToggle[1]);
44
- if (!name) {
45
- sendJson(res, 400, { error: "invalid_skill_name" });
46
- return true;
47
- }
48
- const enabled = skillToggle[2] === "enable";
49
- if (!getSkill(name)) {
50
- sendJson(res, 404, { error: "skill_not_found", name, available: getAllSkills().map((s) => s.name) });
51
- return true;
52
- }
53
- const parsed = await readJsonBody(req);
54
- const scope = parsed.ok ? parsed.value.scope : undefined;
55
- if (scope !== undefined && scope !== "global" && scope !== "project") {
56
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
57
- return true;
58
- }
59
- const [result] = setSkillEnabled([name], enabled, scope ?? "global");
60
- sendJson(res, 200, {
61
- ok: true,
62
- name,
63
- enabled: result.enabled,
64
- scope: scope ?? "global",
65
- ...(result.blockedBy ? { blocked_by: result.blockedBy } : {}),
66
- });
67
- return true;
68
- }
69
- return false;
70
- };
package/dist/serve.js DELETED
@@ -1,138 +0,0 @@
1
- /**
2
- * HTTP API server: exposes chat / models / health for programmatic use.
3
- * Run: min-agent serve [--host 127.0.0.1] [--port 8787]
4
- */
5
- import { createServer } from "http";
6
- import { timingSafeEqual } from "crypto";
7
- import { initMcp, shutdownMcp } from "./mcp.js";
8
- import { discoverSkills } from "./skills.js";
9
- import { loadInstructions } from "./instructions.js";
10
- import { isConfigured } from "./config.js";
11
- import { setAutoApprove } from "./confirm.js";
12
- import { createApprovalGate } from "./http-approvals.js";
13
- import { packageVersion, corsEnabled, corsHeaders, sendJson, } from "./serve/common.js";
14
- import { handleChatRoutes } from "./serve/routes-chat.js";
15
- import { handleSessionRoutes } from "./serve/routes-sessions.js";
16
- import { handleMemoryRoutes } from "./serve/routes-memory.js";
17
- import { handleMcpRoutes } from "./serve/routes-mcp.js";
18
- import { handleSkillRoutes } from "./serve/routes-skills.js";
19
- import { handleMetaRoutes } from "./serve/routes-meta.js";
20
- /**
21
- * With CORS enabled and no auth token, arbitrary web pages could drive the
22
- * agent. Allow only same-machine origins (localhost) in that configuration;
23
- * a token bypasses the origin check entirely.
24
- */
25
- function originAllowed(req) {
26
- if (process.env.MIN_AGENT_SERVE_TOKEN?.trim())
27
- return true;
28
- const origin = req.headers.origin?.trim();
29
- if (!origin || origin === "null")
30
- return true;
31
- try {
32
- const host = new URL(origin).hostname;
33
- if (host === "localhost" || host === "127.0.0.1" || host === "[::1]")
34
- return true;
35
- }
36
- catch {
37
- return false;
38
- }
39
- return false;
40
- }
41
- function authOk(req) {
42
- const token = process.env.MIN_AGENT_SERVE_TOKEN?.trim();
43
- if (!token)
44
- return true;
45
- const h = req.headers.authorization?.trim();
46
- if (!h?.startsWith("Bearer "))
47
- return false;
48
- const provided = Buffer.from(h.slice(7));
49
- const expected = Buffer.from(token);
50
- return provided.length === expected.length && timingSafeEqual(provided, expected);
51
- }
52
- export async function runServe(opts = {}) {
53
- if (!isConfigured()) {
54
- console.error("Not configured. Run: min-agent setup");
55
- process.exit(1);
56
- }
57
- const host = opts.host ?? process.env.MIN_AGENT_SERVE_HOST ?? "127.0.0.1";
58
- const port = opts.port ?? parseInt(process.env.MIN_AGENT_SERVE_PORT ?? "8787", 10);
59
- const isLoopbackHost = host === "127.0.0.1" || host === "::1" || host === "localhost";
60
- if (!isLoopbackHost && !process.env.MIN_AGENT_SERVE_TOKEN?.trim()) {
61
- console.error("Refusing to serve on a non-loopback address without MIN_AGENT_SERVE_TOKEN. " +
62
- "Set the token (Authorization: Bearer <token>) or bind to 127.0.0.1.");
63
- process.exit(1);
64
- }
65
- console.error("\x1b[33m⚠ min-agent serve: confirmations are auto-approved by default (same as -y). Set auto_approve: false with stream: true to require callbacks.\x1b[0m");
66
- setAutoApprove(true);
67
- console.error("\x1b[90m⟳ Initializing MCP, skills, instructions…\x1b[0m");
68
- await initMcp();
69
- discoverSkills();
70
- const ctx = {
71
- host,
72
- version: packageVersion(),
73
- instructions: await loadInstructions(),
74
- approvalGate: createApprovalGate(),
75
- activeChats: 0,
76
- };
77
- const routes = [
78
- handleMetaRoutes,
79
- handleChatRoutes,
80
- handleSessionRoutes,
81
- handleMemoryRoutes,
82
- handleMcpRoutes,
83
- handleSkillRoutes,
84
- ];
85
- const server = createServer(async (req, res) => {
86
- const c = corsHeaders();
87
- if (req.method === "OPTIONS") {
88
- res.writeHead(204, c);
89
- res.end();
90
- return;
91
- }
92
- if (!authOk(req)) {
93
- sendJson(res, 401, {
94
- error: "unauthorized",
95
- detail: "Set Authorization: Bearer <MIN_AGENT_SERVE_TOKEN> when MIN_AGENT_SERVE_TOKEN is set",
96
- });
97
- return;
98
- }
99
- if (corsEnabled() && !originAllowed(req)) {
100
- sendJson(res, 403, {
101
- error: "origin_not_allowed",
102
- detail: "CORS is enabled without a token; only localhost origins are allowed. Set MIN_AGENT_SERVE_TOKEN or disable CORS.",
103
- });
104
- return;
105
- }
106
- const url = new URL(req.url ?? "/", `http://${host}`);
107
- const pathname = url.pathname.replace(/\/$/, "") || "/";
108
- try {
109
- for (const route of routes) {
110
- if (await route(req, res, ctx, url, pathname))
111
- return;
112
- }
113
- sendJson(res, 404, { error: "not_found", path: pathname });
114
- }
115
- catch (err) {
116
- sendJson(res, 500, { error: "internal_error", message: err?.message ?? String(err) });
117
- }
118
- });
119
- await new Promise((resolve, reject) => {
120
- server.once("error", reject);
121
- server.listen(port, host, () => {
122
- console.error(`\x1b[32m✓ min-agent serve\x1b[0m http://${host}:${port} (API: docs/API.md)`);
123
- resolve();
124
- });
125
- });
126
- const closeServer = async () => {
127
- await shutdownMcp();
128
- server.closeAllConnections?.();
129
- await new Promise((resolve) => server.close(() => resolve()));
130
- };
131
- const shutdown = async () => {
132
- await closeServer();
133
- process.exit(0);
134
- };
135
- process.on("SIGINT", shutdown);
136
- process.on("SIGTERM", shutdown);
137
- return { close: closeServer };
138
- }