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,367 +0,0 @@
1
- import { loadInstructions } from "../instructions.js";
2
- import { loadSession, saveSession, withSessionLock } from "../sessions.js";
3
- import { buildUserContent } from "../agent.js";
4
- import { lastUserText } from "../tui/hydrate.js";
5
- import { compactMessages } from "../compaction.js";
6
- import { resolveModel } from "../provider.js";
7
- import { emptyTaskState } from "../tools/todo.js";
8
- import { MAX_CONCURRENT_CHATS, sendJson, sendJsonError, readJsonBody, readSandboxTighten, readThinking, readMemory, normalizeMessages, chatRunOptions, executeChat, } from "./common.js";
9
- /** Shared handler for /v1/chat and /v1/code (streaming + JSON). */
10
- async function handleChatRequest(ctx, req, res) {
11
- if (req.headers["content-type"]?.split(";")[0]?.trim() !== "application/json") {
12
- sendJson(res, 415, { error: "unsupported_media_type", detail: "Use Content-Type: application/json" });
13
- return;
14
- }
15
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
16
- sendJson(res, 429, {
17
- error: "too_many_concurrent_requests",
18
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
19
- });
20
- return;
21
- }
22
- ctx.activeChats++;
23
- try {
24
- const parsed = await readJsonBody(req);
25
- if (!parsed.ok) {
26
- sendJsonError(res, parsed);
27
- return;
28
- }
29
- const body = parsed.value;
30
- const sandboxParsed = readSandboxTighten(body);
31
- if (!sandboxParsed.ok) {
32
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
33
- return;
34
- }
35
- const sandboxTighten = sandboxParsed.policy;
36
- const thinkingParsed = readThinking(body);
37
- if (!thinkingParsed.ok) {
38
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
39
- return;
40
- }
41
- const memoryParsed = readMemory(body);
42
- if (!memoryParsed.ok) {
43
- sendJson(res, 400, { error: memoryParsed.error, detail: memoryParsed.detail });
44
- return;
45
- }
46
- const modelId = typeof body.model === "string" ? body.model : undefined;
47
- const stream = body.stream === true;
48
- const sessionId = typeof body.session_id === "string" ? body.session_id : undefined;
49
- let messages;
50
- if (sessionId) {
51
- await withSessionLock(sessionId, async () => {
52
- const session = loadSession(sessionId);
53
- if (!session) {
54
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
55
- return;
56
- }
57
- if (typeof body.message !== "string" || !body.message.trim()) {
58
- sendJson(res, 400, {
59
- error: "session_requires_message",
60
- detail: "With `session_id`, send a non-empty `message` for the new user turn",
61
- });
62
- return;
63
- }
64
- const messages = [...session.messages];
65
- const content = body.images && body.images.length > 0 ? await buildUserContent(body.message, body.images) : body.message;
66
- messages.push({ role: "user", content });
67
- await executeChat(ctx, {
68
- res,
69
- messages,
70
- modelId,
71
- stream,
72
- sessionId,
73
- runOptions: chatRunOptions(body),
74
- autoApprove: body.auto_approve !== false,
75
- taskState: session.taskState ?? emptyTaskState(),
76
- sandboxTighten,
77
- });
78
- });
79
- return;
80
- }
81
- else {
82
- const norm = normalizeMessages(body);
83
- if (!norm.ok) {
84
- sendJson(res, 400, { error: "invalid_body", detail: norm.error });
85
- return;
86
- }
87
- messages = norm.messages;
88
- if (body.images && body.images.length > 0) {
89
- const last = messages[messages.length - 1];
90
- if (!last || last.role !== "user" || typeof body.message !== "string") {
91
- sendJson(res, 400, {
92
- error: "images_require_message",
93
- detail: "With `images`, send a top-level `message` string for the user turn",
94
- });
95
- return;
96
- }
97
- messages[messages.length - 1] = {
98
- role: "user",
99
- content: await buildUserContent(body.message, body.images),
100
- };
101
- }
102
- }
103
- await executeChat(ctx, {
104
- res,
105
- messages,
106
- modelId,
107
- stream,
108
- sessionId,
109
- runOptions: chatRunOptions(body),
110
- autoApprove: body.auto_approve !== false,
111
- sandboxTighten,
112
- });
113
- }
114
- finally {
115
- ctx.activeChats--;
116
- }
117
- }
118
- export const handleChatRoutes = async (req, res, ctx, _url, pathname) => {
119
- if (req.method === "POST" && pathname === "/v1/chat/reload-instructions") {
120
- ctx.instructions = await loadInstructions();
121
- sendJson(res, 200, { ok: true, instructions_chars: ctx.instructions.join("\n").length });
122
- return true;
123
- }
124
- if (req.method === "POST" && (pathname === "/v1/chat" || pathname === "/v1/code")) {
125
- await handleChatRequest(ctx, req, res);
126
- return true;
127
- }
128
- if (req.method === "POST" && pathname === "/v1/chat/redo") {
129
- const parsed = await readJsonBody(req);
130
- if (!parsed.ok) {
131
- sendJsonError(res, parsed);
132
- return true;
133
- }
134
- const body = parsed.value;
135
- const sessionId = typeof body.session_id === "string" ? body.session_id : undefined;
136
- if (!sessionId) {
137
- sendJson(res, 400, { error: "missing_session_id" });
138
- return true;
139
- }
140
- const sandboxParsed = readSandboxTighten(body);
141
- if (!sandboxParsed.ok) {
142
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
143
- return true;
144
- }
145
- const thinkingParsed = readThinking(body);
146
- if (!thinkingParsed.ok) {
147
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
148
- return true;
149
- }
150
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
151
- sendJson(res, 429, {
152
- error: "too_many_concurrent_requests",
153
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
154
- });
155
- return true;
156
- }
157
- ctx.activeChats++;
158
- try {
159
- await withSessionLock(sessionId, async () => {
160
- const session = loadSession(sessionId);
161
- if (!session) {
162
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
163
- return;
164
- }
165
- const text = lastUserText(session.messages);
166
- if (!text) {
167
- sendJson(res, 400, { error: "no_user_message" });
168
- return;
169
- }
170
- await executeChat(ctx, {
171
- res,
172
- messages: [...session.messages, { role: "user", content: text }],
173
- modelId: typeof body.model === "string" ? body.model : undefined,
174
- stream: body.stream === true,
175
- sessionId,
176
- runOptions: chatRunOptions(body),
177
- autoApprove: body.auto_approve !== false,
178
- taskState: session.taskState ?? emptyTaskState(),
179
- sandboxTighten: sandboxParsed.policy,
180
- });
181
- });
182
- }
183
- finally {
184
- ctx.activeChats--;
185
- }
186
- return true;
187
- }
188
- if (req.method === "POST" && pathname === "/v1/approvals") {
189
- const parsed = await readJsonBody(req);
190
- if (!parsed.ok) {
191
- sendJsonError(res, parsed);
192
- return true;
193
- }
194
- const id = parsed.value.id?.trim();
195
- if (!id) {
196
- sendJson(res, 400, { error: "missing_id" });
197
- return true;
198
- }
199
- if (typeof parsed.value.accepted !== "boolean") {
200
- sendJson(res, 400, { error: "missing_accepted" });
201
- return true;
202
- }
203
- if (!ctx.approvalGate.resolveConfirm(id, parsed.value.accepted)) {
204
- sendJson(res, 404, { error: "unknown_approval_id", id });
205
- return true;
206
- }
207
- sendJson(res, 200, { ok: true, id, accepted: parsed.value.accepted });
208
- return true;
209
- }
210
- if (req.method === "POST" && pathname === "/v1/answers") {
211
- const parsed = await readJsonBody(req);
212
- if (!parsed.ok) {
213
- sendJsonError(res, parsed);
214
- return true;
215
- }
216
- const id = parsed.value.id?.trim();
217
- if (!id) {
218
- sendJson(res, 400, { error: "missing_id" });
219
- return true;
220
- }
221
- const answer = parsed.value.answer === undefined ? null : parsed.value.answer;
222
- if (answer !== null && typeof answer !== "string") {
223
- sendJson(res, 400, { error: "invalid_answer" });
224
- return true;
225
- }
226
- if (!ctx.approvalGate.resolveQuestion(id, answer)) {
227
- sendJson(res, 404, { error: "unknown_answer_id", id });
228
- return true;
229
- }
230
- sendJson(res, 200, { ok: true, id });
231
- return true;
232
- }
233
- if (req.method === "POST" && pathname === "/v1/paste") {
234
- if (req.headers["content-type"]?.split(";")[0]?.trim() !== "application/json") {
235
- sendJson(res, 415, { error: "unsupported_media_type" });
236
- return true;
237
- }
238
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
239
- sendJson(res, 429, {
240
- error: "too_many_concurrent_requests",
241
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
242
- });
243
- return true;
244
- }
245
- const parsed = await readJsonBody(req);
246
- if (!parsed.ok) {
247
- sendJsonError(res, parsed);
248
- return true;
249
- }
250
- const body = parsed.value;
251
- if (!body.image_base64) {
252
- sendJson(res, 400, { error: "missing_image_base64" });
253
- return true;
254
- }
255
- const sandboxParsed = readSandboxTighten(body);
256
- if (!sandboxParsed.ok) {
257
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
258
- return true;
259
- }
260
- const thinkingParsed = readThinking(body);
261
- if (!thinkingParsed.ok) {
262
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
263
- return true;
264
- }
265
- const imageBuffer = Buffer.from(body.image_base64, "base64");
266
- const mimeType = body.mime_type ?? "image/png";
267
- const text = body.message ?? "What's in this image?";
268
- const modelId = body.model;
269
- const sessionId = body.session_id;
270
- const stream = body.stream === true;
271
- ctx.activeChats++;
272
- try {
273
- await withSessionLock(sessionId, async () => {
274
- let messages = [];
275
- let pasteTaskState;
276
- if (sessionId) {
277
- const session = loadSession(sessionId);
278
- if (!session) {
279
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
280
- return;
281
- }
282
- messages = [...session.messages];
283
- pasteTaskState = session.taskState;
284
- }
285
- messages.push({
286
- role: "user",
287
- content: [
288
- { type: "text", text },
289
- { type: "image", image: imageBuffer, mimeType },
290
- ],
291
- });
292
- await executeChat(ctx, {
293
- res,
294
- messages,
295
- modelId,
296
- stream,
297
- sessionId,
298
- runOptions: chatRunOptions(body),
299
- autoApprove: body.auto_approve !== false,
300
- taskState: pasteTaskState ?? emptyTaskState(),
301
- sandboxTighten: sandboxParsed.policy,
302
- });
303
- });
304
- }
305
- finally {
306
- ctx.activeChats--;
307
- }
308
- return true;
309
- }
310
- if (req.method === "POST" && pathname === "/v1/chat/compact") {
311
- const parsed = await readJsonBody(req);
312
- if (!parsed.ok) {
313
- sendJsonError(res, parsed);
314
- return true;
315
- }
316
- const body = parsed.value;
317
- if (!body.session_id) {
318
- sendJson(res, 400, { error: "missing_session_id" });
319
- return true;
320
- }
321
- const session = loadSession(body.session_id);
322
- if (!session) {
323
- sendJson(res, 404, { error: "session_not_found" });
324
- return true;
325
- }
326
- const model = resolveModel();
327
- const result = await compactMessages(session.messages, model, { taskGoal: session.taskState?.goal });
328
- if (result.compacted) {
329
- saveSession(result.messages, body.session_id);
330
- }
331
- sendJson(res, 200, { ok: true, compacted: result.compacted, message_count: result.messages.length });
332
- return true;
333
- }
334
- if (req.method === "POST" && pathname === "/v1/chat/undo") {
335
- const parsed = await readJsonBody(req);
336
- if (!parsed.ok) {
337
- sendJsonError(res, parsed);
338
- return true;
339
- }
340
- const body = parsed.value;
341
- if (!body.session_id) {
342
- sendJson(res, 400, { error: "missing_session_id" });
343
- return true;
344
- }
345
- const session = loadSession(body.session_id);
346
- if (!session) {
347
- sendJson(res, 404, { error: "session_not_found" });
348
- return true;
349
- }
350
- let userIdx = -1;
351
- for (let i = session.messages.length - 1; i >= 0; i--) {
352
- if (session.messages[i].role === "user") {
353
- userIdx = i;
354
- break;
355
- }
356
- }
357
- if (userIdx === -1) {
358
- sendJson(res, 400, { error: "no_user_message" });
359
- return true;
360
- }
361
- session.messages = session.messages.slice(0, userIdx + 1);
362
- saveSession(session.messages, body.session_id);
363
- sendJson(res, 200, { ok: true, message_count: session.messages.length, messages: session.messages });
364
- return true;
365
- }
366
- return false;
367
- };
@@ -1,212 +0,0 @@
1
- import { getMcpStatus, syncMcpConnections, loadMcpConfigEntries, listMcpCatalog, readMcpResource, getMcpPrompt, } from "../mcp.js";
2
- import { sendJson, sendJsonError, readJsonBody, decodePathSegment } from "./common.js";
3
- export const handleMcpRoutes = async (req, res, _ctx, url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/mcp") {
5
- const status = getMcpStatus();
6
- sendJson(res, 200, { servers: status });
7
- return true;
8
- }
9
- const mcpResources = pathname.match(/^\/v1\/mcp\/([^/]+)\/resources$/);
10
- if (req.method === "GET" && mcpResources) {
11
- const name = decodePathSegment(mcpResources[1]);
12
- if (!name) {
13
- sendJson(res, 400, { error: "missing_name" });
14
- return true;
15
- }
16
- const status = getMcpStatus()[name];
17
- if (!status) {
18
- sendJson(res, 404, { error: "mcp_not_found", name });
19
- return true;
20
- }
21
- if (!status.connected) {
22
- sendJson(res, 404, { error: "mcp_not_connected", name });
23
- return true;
24
- }
25
- const catalog = listMcpCatalog(name).servers[0];
26
- sendJson(res, 200, { name, resources: catalog.resources, templates: catalog.templates });
27
- return true;
28
- }
29
- const mcpPrompts = pathname.match(/^\/v1\/mcp\/([^/]+)\/prompts$/);
30
- if (req.method === "GET" && mcpPrompts) {
31
- const name = decodePathSegment(mcpPrompts[1]);
32
- if (!name) {
33
- sendJson(res, 400, { error: "missing_name" });
34
- return true;
35
- }
36
- const status = getMcpStatus()[name];
37
- if (!status) {
38
- sendJson(res, 404, { error: "mcp_not_found", name });
39
- return true;
40
- }
41
- if (!status.connected) {
42
- sendJson(res, 404, { error: "mcp_not_connected", name });
43
- return true;
44
- }
45
- const catalog = listMcpCatalog(name).servers[0];
46
- sendJson(res, 200, { name, prompts: catalog.prompts });
47
- return true;
48
- }
49
- const mcpRead = pathname.match(/^\/v1\/mcp\/([^/]+)\/resources\/read$/);
50
- if (req.method === "POST" && mcpRead) {
51
- const name = decodePathSegment(mcpRead[1]);
52
- if (!name) {
53
- sendJson(res, 400, { error: "missing_name" });
54
- return true;
55
- }
56
- const parsed = await readJsonBody(req);
57
- if (!parsed.ok) {
58
- sendJsonError(res, parsed);
59
- return true;
60
- }
61
- const uri = parsed.value.uri?.trim();
62
- if (!uri) {
63
- sendJson(res, 400, { error: "missing_uri" });
64
- return true;
65
- }
66
- const status = getMcpStatus()[name];
67
- if (!status) {
68
- sendJson(res, 404, { error: "mcp_not_found", name });
69
- return true;
70
- }
71
- if (!status.connected) {
72
- sendJson(res, 404, { error: "mcp_not_connected", name });
73
- return true;
74
- }
75
- const content = await readMcpResource(uri, name);
76
- sendJson(res, 200, { ok: true, name, uri, content });
77
- return true;
78
- }
79
- const mcpGetPrompt = pathname.match(/^\/v1\/mcp\/([^/]+)\/prompts\/get$/);
80
- if (req.method === "POST" && mcpGetPrompt) {
81
- const name = decodePathSegment(mcpGetPrompt[1]);
82
- if (!name) {
83
- sendJson(res, 400, { error: "missing_name" });
84
- return true;
85
- }
86
- const parsed = await readJsonBody(req);
87
- if (!parsed.ok) {
88
- sendJsonError(res, parsed);
89
- return true;
90
- }
91
- const prompt = parsed.value.prompt?.trim();
92
- if (!prompt) {
93
- sendJson(res, 400, { error: "missing_prompt" });
94
- return true;
95
- }
96
- const status = getMcpStatus()[name];
97
- if (!status) {
98
- sendJson(res, 404, { error: "mcp_not_found", name });
99
- return true;
100
- }
101
- if (!status.connected) {
102
- sendJson(res, 404, { error: "mcp_not_connected", name });
103
- return true;
104
- }
105
- const content = await getMcpPrompt(prompt, parsed.value.arguments, name);
106
- sendJson(res, 200, { ok: true, name, prompt, content });
107
- return true;
108
- }
109
- if (req.method === "POST" && pathname === "/v1/mcp") {
110
- const parsed = await readJsonBody(req);
111
- if (!parsed.ok) {
112
- sendJsonError(res, parsed);
113
- return true;
114
- }
115
- const body = parsed.value;
116
- if (!body.name) {
117
- sendJson(res, 400, { error: "missing_name" });
118
- return true;
119
- }
120
- if (body.remoteTransport && !["streamable-http", "sse", "auto"].includes(body.remoteTransport)) {
121
- sendJson(res, 400, { error: "invalid_remote_transport", allowed: ["streamable-http", "sse", "auto"] });
122
- return true;
123
- }
124
- const scope = body.project ? "project" : "global";
125
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
126
- const config = loadMcpConfigFile(scope);
127
- const common = {
128
- ...(body.environment ? { environment: body.environment } : {}),
129
- ...(body.headers ? { headers: body.headers } : {}),
130
- ...(body.remoteTransport ? { remoteTransport: body.remoteTransport } : {}),
131
- ...(body.timeout !== undefined ? { timeout: body.timeout } : {}),
132
- ...(body.connectTimeout !== undefined ? { connectTimeout: body.connectTimeout } : {}),
133
- ...(body.callTimeout !== undefined ? { callTimeout: body.callTimeout } : {}),
134
- enabled: body.enabled !== false,
135
- };
136
- if (body.url) {
137
- config.mcpServers[body.name] = { url: body.url, ...(body.token ? { token: body.token } : {}), ...common };
138
- }
139
- else if (typeof body.command === "string" && body.command.length > 0) {
140
- config.mcpServers[body.name] = { command: body.command, ...(body.args ? { args: body.args } : {}), ...common };
141
- }
142
- else if (Array.isArray(body.command) && body.command.length > 0) {
143
- config.mcpServers[body.name] = { command: body.command, ...common };
144
- }
145
- else {
146
- sendJson(res, 400, { error: "provide command or url" });
147
- return true;
148
- }
149
- saveMcpConfig(config, scope);
150
- await syncMcpConnections();
151
- const status = getMcpStatus()[body.name];
152
- sendJson(res, 201, {
153
- ok: true,
154
- name: body.name,
155
- scope,
156
- connected: status?.connected ?? false,
157
- ...(status?.error ? { error: status.error } : {}),
158
- ...(status?.tools?.length ? { tools: status.tools } : {}),
159
- });
160
- return true;
161
- }
162
- if (req.method === "DELETE" && pathname.startsWith("/v1/mcp/")) {
163
- const name = decodePathSegment(pathname.slice("/v1/mcp/".length));
164
- if (!name) {
165
- sendJson(res, 400, { error: "missing_name" });
166
- return true;
167
- }
168
- const scope = url.searchParams.get("project") === "1" ? "project" : "global";
169
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
170
- const config = loadMcpConfigFile(scope);
171
- if (!config.mcpServers[name]) {
172
- sendJson(res, 404, { error: "mcp_not_found", name, scope, available: Object.keys(config.mcpServers) });
173
- return true;
174
- }
175
- delete config.mcpServers[name];
176
- saveMcpConfig(config, scope);
177
- await syncMcpConnections();
178
- sendJson(res, 200, { ok: true, deleted: name, scope });
179
- return true;
180
- }
181
- const mcpToggle = pathname.match(/^\/v1\/mcp\/([^/]+)\/(enable|disable)$/);
182
- if (req.method === "POST" && mcpToggle) {
183
- const name = decodePathSegment(mcpToggle[1]);
184
- if (!name) {
185
- sendJson(res, 400, { error: "missing_name" });
186
- return true;
187
- }
188
- const enabled = mcpToggle[2] === "enable";
189
- const parsed = await readJsonBody(req);
190
- const scope = parsed.ok && parsed.value.scope === "project" ? "project" : "global";
191
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
192
- const config = loadMcpConfigFile(scope);
193
- if (!config.mcpServers[name]) {
194
- const known = loadMcpConfigEntries().some((e) => e.name === name);
195
- sendJson(res, 404, {
196
- error: "mcp_not_found",
197
- name,
198
- scope,
199
- ...(known
200
- ? { hint: `server exists in another scope; pass the matching scope` }
201
- : { available: Object.keys(config.mcpServers) }),
202
- });
203
- return true;
204
- }
205
- config.mcpServers[name].enabled = enabled;
206
- saveMcpConfig(config, scope);
207
- await syncMcpConnections();
208
- sendJson(res, 200, { ok: true, name, enabled, scope, connected: getMcpStatus()[name]?.connected ?? false });
209
- return true;
210
- }
211
- return false;
212
- };
@@ -1,95 +0,0 @@
1
- import { loadMemories, addMemory, deleteMemory, searchMemories, readMemoryScope, parseMemoryMode, setMemoryMode, memoryPayload, } from "../memory.js";
2
- import { sendJson, sendJsonError, readJsonBody } from "./common.js";
3
- export const handleMemoryRoutes = async (req, res, _ctx, url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/memory/mode") {
5
- sendJson(res, 200, memoryPayload());
6
- return true;
7
- }
8
- if (req.method === "POST" && pathname === "/v1/memory/mode") {
9
- const parsed = await readJsonBody(req);
10
- if (!parsed.ok) {
11
- sendJsonError(res, parsed);
12
- return true;
13
- }
14
- const mode = parseMemoryMode(parsed.value.memory);
15
- if (!mode) {
16
- sendJson(res, 400, { error: "invalid_memory", detail: "memory must be on or off" });
17
- return true;
18
- }
19
- const scopeParsed = readMemoryScope(parsed.value.scope);
20
- if (!scopeParsed.ok) {
21
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
22
- return true;
23
- }
24
- const scope = scopeParsed.scope ?? "global";
25
- setMemoryMode(mode, scope);
26
- sendJson(res, 200, { ok: true, scope, ...memoryPayload() });
27
- return true;
28
- }
29
- if (req.method === "GET" && pathname === "/v1/memory") {
30
- sendJson(res, 200, {
31
- ...memoryPayload(),
32
- memories: loadMemories("global"),
33
- project_memories: loadMemories("project"),
34
- });
35
- return true;
36
- }
37
- if (req.method === "POST" && pathname === "/v1/memory") {
38
- const parsed = await readJsonBody(req);
39
- if (!parsed.ok) {
40
- sendJsonError(res, parsed);
41
- return true;
42
- }
43
- const body = parsed.value;
44
- if (!body.content) {
45
- sendJson(res, 400, { error: "missing_content" });
46
- return true;
47
- }
48
- const scopeParsed = readMemoryScope(body.scope);
49
- if (!scopeParsed.ok) {
50
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
51
- return true;
52
- }
53
- const scope = scopeParsed.scope ?? "global";
54
- const memory = addMemory(body.content, body.tags ?? [], scope);
55
- sendJson(res, 201, { ok: true, memory, scope });
56
- return true;
57
- }
58
- if (req.method === "GET" && pathname === "/v1/memory/search") {
59
- const query = url.searchParams.get("q") ?? "";
60
- if (!query) {
61
- sendJson(res, 400, { error: "missing_query_param_q" });
62
- return true;
63
- }
64
- const scopeParsed = readMemoryScope(url.searchParams.get("scope"));
65
- if (!scopeParsed.ok) {
66
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
67
- return true;
68
- }
69
- const globalResults = scopeParsed.scope === "project" ? [] : searchMemories(query, "global");
70
- const projectResults = scopeParsed.scope === "global" ? [] : searchMemories(query, "project");
71
- sendJson(res, 200, { results: globalResults, project_results: projectResults });
72
- return true;
73
- }
74
- if (req.method === "DELETE" && pathname.startsWith("/v1/memory/")) {
75
- const idx = parseInt(pathname.slice("/v1/memory/".length), 10);
76
- if (Number.isNaN(idx)) {
77
- sendJson(res, 400, { error: "invalid_index" });
78
- return true;
79
- }
80
- const scopeParsed = readMemoryScope(url.searchParams.get("scope"));
81
- if (!scopeParsed.ok) {
82
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
83
- return true;
84
- }
85
- const scope = scopeParsed.scope ?? "global";
86
- const ok = deleteMemory(idx - 1, scope);
87
- if (!ok) {
88
- sendJson(res, 404, { error: "memory_not_found", index: idx, scope });
89
- return true;
90
- }
91
- sendJson(res, 200, { ok: true, deleted: idx, scope });
92
- return true;
93
- }
94
- return false;
95
- };