min-agent 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -18
- package/dist/cli.js +538 -12
- package/docs/API.md +25 -8
- package/package.json +6 -6
- package/skills/self-config/SKILL.md +7 -3
- package/skills/self-config/reference.md +8 -4
- package/dist/agent.js +0 -1342
- package/dist/assistant-stream.js +0 -131
- package/dist/cli/commands/chat.js +0 -13
- package/dist/cli/commands/exec.js +0 -35
- package/dist/cli/commands/history.js +0 -58
- package/dist/cli/commands/index.js +0 -241
- package/dist/cli/commands/init.js +0 -18
- package/dist/cli/commands/mcp.js +0 -173
- package/dist/cli/commands/memory.js +0 -87
- package/dist/cli/commands/models.js +0 -21
- package/dist/cli/commands/permission.js +0 -12
- package/dist/cli/commands/rules.js +0 -33
- package/dist/cli/commands/sandbox.js +0 -13
- package/dist/cli/commands/serve.js +0 -9
- package/dist/cli/commands/setup.js +0 -4
- package/dist/cli/commands/shared.js +0 -16
- package/dist/cli/commands/skills.js +0 -119
- package/dist/cli/commands/think.js +0 -12
- package/dist/cli/commands/update.js +0 -7
- package/dist/cli/commands/write-config.js +0 -52
- package/dist/cli/errors.js +0 -36
- package/dist/cli/exec-prompt.js +0 -26
- package/dist/cli/option-helpers.js +0 -65
- package/dist/cli/program.js +0 -217
- package/dist/clipboard.js +0 -142
- package/dist/code-mode.js +0 -184
- package/dist/compaction.js +0 -644
- package/dist/config.js +0 -470
- package/dist/confirm.js +0 -172
- package/dist/context-window.js +0 -246
- package/dist/doom-loop.js +0 -278
- package/dist/fetch-timeout.js +0 -152
- package/dist/http-approvals.js +0 -60
- package/dist/http.js +0 -119
- package/dist/instructions.js +0 -196
- package/dist/logger.js +0 -95
- package/dist/markdown.js +0 -259
- package/dist/mcp.js +0 -1036
- package/dist/memory-cli.js +0 -33
- package/dist/memory.js +0 -295
- package/dist/model-catalog.js +0 -285
- package/dist/output.js +0 -76
- package/dist/paste-handler.js +0 -41
- package/dist/permission-cli.js +0 -40
- package/dist/plugins.js +0 -131
- package/dist/pricing.js +0 -119
- package/dist/provider.js +0 -63
- package/dist/question-format.js +0 -60
- package/dist/reasoning-stream.js +0 -158
- package/dist/sandbox-cli.js +0 -79
- package/dist/sandbox.js +0 -403
- package/dist/save-throttle.js +0 -45
- package/dist/scope.js +0 -23
- package/dist/serve/common.js +0 -425
- package/dist/serve/routes-chat.js +0 -367
- package/dist/serve/routes-mcp.js +0 -212
- package/dist/serve/routes-memory.js +0 -95
- package/dist/serve/routes-meta.js +0 -233
- package/dist/serve/routes-sessions.js +0 -61
- package/dist/serve/routes-skills.js +0 -70
- package/dist/serve.js +0 -138
- package/dist/sessions.js +0 -265
- package/dist/skills.js +0 -599
- package/dist/structured-output.js +0 -29
- package/dist/synthetic.js +0 -7
- package/dist/think-cli.js +0 -36
- package/dist/thinking-wire.js +0 -228
- package/dist/thinking.js +0 -142
- package/dist/title-gen.js +0 -55
- package/dist/token-display.js +0 -39
- package/dist/tool-display.js +0 -178
- package/dist/tool-output.js +0 -126
- package/dist/tools/apply_patch.js +0 -265
- package/dist/tools/atomic-file.js +0 -35
- package/dist/tools/backend.js +0 -61
- package/dist/tools/bash.js +0 -231
- package/dist/tools/code_search.js +0 -98
- package/dist/tools/edit.js +0 -68
- package/dist/tools/explore.js +0 -232
- package/dist/tools/glob.js +0 -40
- package/dist/tools/grep.js +0 -174
- package/dist/tools/index.js +0 -34
- package/dist/tools/question.js +0 -54
- package/dist/tools/read.js +0 -109
- package/dist/tools/search-searxng.js +0 -223
- package/dist/tools/search-serper.js +0 -189
- package/dist/tools/task.js +0 -165
- package/dist/tools/todo.js +0 -213
- package/dist/tools/web_fetch.js +0 -195
- package/dist/tools/web_search.js +0 -228
- package/dist/tools/write.js +0 -55
- package/dist/tui/App.js +0 -126
- package/dist/tui/ConfirmBar.js +0 -66
- package/dist/tui/InputBar.js +0 -616
- package/dist/tui/MessageList.js +0 -718
- package/dist/tui/ModelPicker.js +0 -113
- package/dist/tui/QuestionBar.js +0 -136
- package/dist/tui/SessionPicker.js +0 -79
- package/dist/tui/Spinner.js +0 -6
- package/dist/tui/StatusBar.js +0 -22
- package/dist/tui/ThinkPicker.js +0 -77
- package/dist/tui/agent-runner.js +0 -223
- package/dist/tui/bracketed-paste.js +0 -37
- package/dist/tui/caret-pos.js +0 -179
- package/dist/tui/caret.js +0 -69
- package/dist/tui/click-count.js +0 -13
- package/dist/tui/diff-view.js +0 -61
- package/dist/tui/drag-state.js +0 -49
- package/dist/tui/hydrate.js +0 -129
- package/dist/tui/index.js +0 -258
- package/dist/tui/input-history.js +0 -125
- package/dist/tui/layout.js +0 -105
- package/dist/tui/mouse.js +0 -46
- package/dist/tui/overlay-input.js +0 -12
- package/dist/tui/paste-draft.js +0 -173
- package/dist/tui/prompt-queue.js +0 -24
- package/dist/tui/selection.js +0 -232
- package/dist/tui/session-switch.js +0 -28
- package/dist/tui/slash-commands.js +0 -123
- package/dist/tui/slash-handler.js +0 -589
- package/dist/tui/text-width.js +0 -113
- package/dist/tui/theme.js +0 -12
- package/dist/tui/token-info.js +0 -7
- package/dist/tui/tool-children.js +0 -19
- package/dist/tui/types.js +0 -1
- package/dist/tui/undo-stack.js +0 -14
- package/dist/tui/use-sgr-mouse.js +0 -29
- package/dist/tui-chat.js +0 -456
- package/dist/updater.js +0 -116
- package/dist/xml-search.js +0 -194
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
- package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
|
@@ -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
|
-
}
|