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