min-agent 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,233 +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 { getModelPrice, estimateCost } from "../pricing.js";
9
- import { checkForUpdate } from "../updater.js";
10
- import { sendJson, sendJsonError, readJsonBody, readStringPathList, sandboxPayload, permissionPayload, thinkingPayload, memoryPayload, } from "./common.js";
11
- export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
12
- if (req.method === "GET" && pathname === "/health") {
13
- sendJson(res, 200, { ok: true, service: "min-agent", version: ctx.version });
14
- return true;
15
- }
16
- if (req.method === "GET" && pathname === "/v1/meta") {
17
- sendJson(res, 200, {
18
- version: ctx.version,
19
- cwd: process.cwd(),
20
- instructions_chars: ctx.instructions.join("\n").length,
21
- sandbox: sandboxPayload(),
22
- permission: permissionPayload(),
23
- thinking: thinkingPayload(),
24
- memory: memoryPayload(),
25
- });
26
- return true;
27
- }
28
- if (req.method === "GET" && pathname === "/v1/update") {
29
- const check = await checkForUpdate({ currentVersion: ctx.version });
30
- sendJson(res, 200, {
31
- current: check.current,
32
- latest: check.latest,
33
- update_available: check.updateAvailable,
34
- ...(check.latest === null ? { error: "registry_unavailable" } : {}),
35
- });
36
- return true;
37
- }
38
- if (req.method === "GET" && pathname === "/v1/models") {
39
- const config = getEffectiveConfig();
40
- const provider = getActiveProvider(config);
41
- const base = provider?.baseURL;
42
- const key = provider?.apiKey;
43
- if (!base || !key) {
44
- sendJson(res, 500, { error: "provider_not_configured" });
45
- return true;
46
- }
47
- const models = await fetchModels(base, key);
48
- sendJson(res, 200, {
49
- default_model: provider?.defaultModel ?? null,
50
- models,
51
- });
52
- return true;
53
- }
54
- if (req.method === "GET" && pathname === "/v1/budget") {
55
- const snap = getBudgetSnapshot();
56
- sendJson(res, 200, { maxCostUSD: snap.maxCostUSD ?? null, source: snap.source });
57
- return true;
58
- }
59
- if (req.method === "POST" && pathname === "/v1/budget") {
60
- const parsed = await readJsonBody(req);
61
- if (!parsed.ok) {
62
- sendJsonError(res, parsed);
63
- return true;
64
- }
65
- const n = parsed.value.maxCostUSD;
66
- if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) {
67
- sendJson(res, 400, { error: "invalid_max_cost", detail: "maxCostUSD must be a positive number" });
68
- return true;
69
- }
70
- const scopeParsed = readScope(parsed.value.scope);
71
- if (!scopeParsed.ok) {
72
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
73
- return true;
74
- }
75
- const scope = scopeParsed.scope ?? "global";
76
- setBudgetMaxCost(n, scope);
77
- const snap = getBudgetSnapshot();
78
- sendJson(res, 200, { ok: true, maxCostUSD: snap.maxCostUSD ?? n, source: snap.source, scope });
79
- return true;
80
- }
81
- if (req.method === "GET" && pathname === "/v1/sandbox") {
82
- sendJson(res, 200, sandboxPayload());
83
- return true;
84
- }
85
- if (req.method === "GET" && pathname === "/v1/permission") {
86
- sendJson(res, 200, permissionPayload());
87
- return true;
88
- }
89
- if (req.method === "POST" && pathname === "/v1/permission") {
90
- const parsed = await readJsonBody(req);
91
- if (!parsed.ok) {
92
- sendJsonError(res, parsed);
93
- return true;
94
- }
95
- const mode = parsePermissionMode(parsed.value.permission);
96
- if (!mode) {
97
- sendJson(res, 400, { error: "invalid_permission", detail: "permission must be ask, accept-edits, or allow-all" });
98
- return true;
99
- }
100
- const scopeParsed = readScope(parsed.value.scope);
101
- if (!scopeParsed.ok) {
102
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
103
- return true;
104
- }
105
- const scope = scopeParsed.scope ?? "global";
106
- setPermissionMode(mode, scope);
107
- sendJson(res, 200, { ok: true, scope, ...permissionPayload() });
108
- return true;
109
- }
110
- if (req.method === "GET" && pathname === "/v1/thinking") {
111
- sendJson(res, 200, thinkingPayload());
112
- return true;
113
- }
114
- if (req.method === "POST" && pathname === "/v1/thinking") {
115
- const parsed = await readJsonBody(req);
116
- if (!parsed.ok) {
117
- sendJsonError(res, parsed);
118
- return true;
119
- }
120
- const effort = parseThinkingEffort(parsed.value.thinking);
121
- if (!effort) {
122
- sendJson(res, 400, { error: "invalid_thinking", detail: "thinking must be off, low, medium, high, or max" });
123
- return true;
124
- }
125
- const scopeParsed = readScope(parsed.value.scope);
126
- if (!scopeParsed.ok) {
127
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
128
- return true;
129
- }
130
- const scope = scopeParsed.scope ?? "global";
131
- setThinkingEffort(effort, scope);
132
- sendJson(res, 200, { ok: true, scope, ...thinkingPayload() });
133
- return true;
134
- }
135
- if (req.method === "POST" && pathname === "/v1/sandbox") {
136
- const parsed = await readJsonBody(req);
137
- if (!parsed.ok) {
138
- sendJsonError(res, parsed);
139
- return true;
140
- }
141
- const body = parsed.value;
142
- const mode = body.mode === undefined ? undefined : parseSandboxMode(body.mode);
143
- if (body.mode !== undefined && !mode) {
144
- sendJson(res, 400, { error: "invalid_sandbox", detail: "mode must be off, workspace, or strict" });
145
- return true;
146
- }
147
- const network = body.network === undefined ? undefined : parseNetworkPolicy(body.network);
148
- if (body.network !== undefined && !network) {
149
- sendJson(res, 400, { error: "invalid_network", detail: "network must be allow or deny" });
150
- return true;
151
- }
152
- const extraWrite = readStringPathList(body.extraWriteRoots);
153
- const extraRead = readStringPathList(body.extraReadRoots);
154
- if (!extraWrite.ok || !extraRead.ok) {
155
- sendJson(res, 400, {
156
- error: "invalid_roots",
157
- detail: "extraWriteRoots and extraReadRoots must be arrays of non-empty strings",
158
- });
159
- return true;
160
- }
161
- if (!mode && !network && extraWrite.value === undefined && extraRead.value === undefined) {
162
- sendJson(res, 400, { error: "invalid_body", detail: "Provide mode, network, extraWriteRoots, or extraReadRoots" });
163
- return true;
164
- }
165
- const scopeParsed = readScope(body.scope);
166
- if (!scopeParsed.ok) {
167
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
168
- return true;
169
- }
170
- const scope = scopeParsed.scope ?? "global";
171
- setSandboxConfig({
172
- ...(mode ? { mode } : {}),
173
- ...(network ? { network } : {}),
174
- ...(extraWrite.value ? { extraWriteRoots: extraWrite.value } : {}),
175
- ...(extraRead.value ? { extraReadRoots: extraRead.value } : {}),
176
- }, scope);
177
- sendJson(res, 200, { ok: true, scope, ...sandboxPayload() });
178
- return true;
179
- }
180
- if (req.method === "GET" && pathname === "/v1/diff") {
181
- const result = await getWorkingTreeDiff();
182
- if (!result.ok) {
183
- sendJson(res, 200, { ok: false, reason: result.reason, message: result.message });
184
- return true;
185
- }
186
- sendJson(res, 200, { ok: true, diff: result.diff });
187
- return true;
188
- }
189
- if (req.method === "GET" && pathname === "/v1/rules") {
190
- sendJson(res, 200, {
191
- count: ctx.instructions.length,
192
- rules: ctx.instructions.map((inst) => {
193
- const firstLine = inst.split("\n")[0] ?? "";
194
- return { source: firstLine.replace("Instructions from: ", ""), chars: inst.length };
195
- }),
196
- });
197
- return true;
198
- }
199
- if (req.method === "GET" && pathname === "/v1/project") {
200
- const project = scanProject();
201
- sendJson(res, 200, { project });
202
- return true;
203
- }
204
- if (req.method === "GET" && pathname === "/v1/context") {
205
- const config = getEffectiveConfig();
206
- const provider = getActiveProvider(config);
207
- const info = await getContextWindowInfo(provider?.defaultModel);
208
- sendJson(res, 200, {
209
- context_window: info.tokens,
210
- source: info.source,
211
- model: provider?.defaultModel ?? null,
212
- });
213
- return true;
214
- }
215
- if (req.method === "GET" && pathname === "/v1/cost") {
216
- const config = getEffectiveConfig();
217
- const provider = getActiveProvider(config);
218
- const modelId = url.searchParams.get("model") ?? provider?.defaultModel ?? null;
219
- const inputRaw = url.searchParams.get("input");
220
- const outputRaw = url.searchParams.get("output");
221
- const input = inputRaw ? parseInt(inputRaw, 10) : 0;
222
- const output = outputRaw ? parseInt(outputRaw, 10) : 0;
223
- if (Number.isNaN(input) || Number.isNaN(output) || input < 0 || output < 0) {
224
- sendJson(res, 400, { error: "invalid_tokens", detail: "input/output must be non-negative integers" });
225
- return true;
226
- }
227
- const price = modelId ? await getModelPrice(modelId) : null;
228
- const cost = price ? estimateCost({ inputTokens: input, outputTokens: output }, price) : null;
229
- sendJson(res, 200, { model: modelId, price, cost_usd: cost });
230
- return true;
231
- }
232
- return false;
233
- };
@@ -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
- }