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,33 +0,0 @@
1
- import { takeScopeFlags, scopeLabel } from "./scope.js";
2
- import { parseMemoryMode, resolveMemoryMode, setMemoryMode, setMemoryOverride, memoryModeLabel, memorySourceLabel, } from "./memory.js";
3
- export const MEMORY_CLI_USAGE = [
4
- "Usage: min-agent memory [on|off] [--project|--global]",
5
- " min-agent --memory on|off [--project|--global]",
6
- ].join("\n");
7
- export function runMemoryCli(input) {
8
- const { scope: posScope, rest } = takeScopeFlags(input.positionals);
9
- const scope = posScope ?? input.scope ?? "global";
10
- if (rest[0] === "--help" || rest[0] === "-h") {
11
- return { ok: true, lines: [MEMORY_CLI_USAGE] };
12
- }
13
- if (rest.length === 1) {
14
- const parsed = parseMemoryMode(rest[0]);
15
- if (!parsed)
16
- return { ok: false, lines: [MEMORY_CLI_USAGE] };
17
- setMemoryMode(parsed, scope);
18
- setMemoryOverride(parsed);
19
- return { ok: true, lines: [`✓ Memory set to ${memoryModeLabel(parsed)} (${scopeLabel(scope)})`] };
20
- }
21
- if (rest.length > 1)
22
- return { ok: false, lines: [MEMORY_CLI_USAGE] };
23
- if (input.flagMode) {
24
- setMemoryMode(input.flagMode, scope);
25
- setMemoryOverride(input.flagMode);
26
- return { ok: true, lines: [`✓ Memory set to ${memoryModeLabel(input.flagMode)} (${scopeLabel(scope)})`] };
27
- }
28
- const { memory, source } = resolveMemoryMode();
29
- return {
30
- ok: true,
31
- lines: [`Current memory: ${memoryModeLabel(memory)} (${memorySourceLabel(source)})`, MEMORY_CLI_USAGE],
32
- };
33
- }
package/dist/memory.js DELETED
@@ -1,295 +0,0 @@
1
- import { readFileSync, mkdirSync, existsSync, renameSync, statSync } from "fs";
2
- import path from "path";
3
- import { tool, jsonSchema } from "ai";
4
- import { getConfigDir, getProjectConfigDir, loadConfig, loadProjectConfig, saveConfig, saveProjectConfig, parseMemoryMode, } from "./config.js";
5
- import { atomicWriteFileSync } from "./tools/atomic-file.js";
6
- import { readScope, takeScopeFlags } from "./scope.js";
7
- export { parseMemoryMode, readScope as readMemoryScope, takeScopeFlags };
8
- export const DEFAULT_MEMORY_MODE = "off";
9
- const MAX_INJECTED_MEMORIES = 30;
10
- let memoryOverride;
11
- export function setMemoryOverride(mode) {
12
- memoryOverride = mode;
13
- }
14
- export function getMemoryOverride() {
15
- return memoryOverride;
16
- }
17
- export function memoryModeLabel(mode) {
18
- return mode;
19
- }
20
- export function memorySourceLabel(source) {
21
- if (source === "cli")
22
- return "this run";
23
- if (source === "project")
24
- return "project";
25
- if (source === "global")
26
- return "global";
27
- return "default";
28
- }
29
- export function getMemorySnapshot() {
30
- const project = parseMemoryMode(loadProjectConfig().memory);
31
- if (project !== undefined)
32
- return { memory: project, source: "project" };
33
- const global = parseMemoryMode(loadConfig().memory);
34
- if (global !== undefined)
35
- return { memory: global, source: "global" };
36
- return { memory: undefined, source: null };
37
- }
38
- export function resolveMemoryMode(runOverride) {
39
- if (runOverride)
40
- return { memory: runOverride, source: "cli" };
41
- const override = getMemoryOverride();
42
- if (override)
43
- return { memory: override, source: "cli" };
44
- const snap = getMemorySnapshot();
45
- return { memory: snap.memory ?? DEFAULT_MEMORY_MODE, source: snap.source };
46
- }
47
- export function isMemoryEnabled(runOverride) {
48
- return resolveMemoryMode(runOverride).memory === "on";
49
- }
50
- function clearProjectMemoryMode() {
51
- const project = loadProjectConfig();
52
- if (project.memory === undefined)
53
- return;
54
- const { memory: _removed, ...rest } = project;
55
- saveProjectConfig(rest);
56
- }
57
- export function setMemoryMode(mode, scope) {
58
- if (scope === "project") {
59
- saveProjectConfig({ ...loadProjectConfig(), memory: mode });
60
- return;
61
- }
62
- saveConfig({ ...loadConfig(), memory: mode });
63
- clearProjectMemoryMode();
64
- }
65
- export function memoryPayload() {
66
- const { memory, source } = resolveMemoryMode();
67
- return { memory, source, label: memoryModeLabel(memory) };
68
- }
69
- export function defaultMemoryScope() {
70
- return existsSync(getProjectConfigDir()) ? "project" : "global";
71
- }
72
- function resolveStore(scope) {
73
- return scope === "global" || scope === "project" ? scope : defaultMemoryScope();
74
- }
75
- function getMemoryFile(scope) {
76
- if (scope === "project")
77
- return path.join(getProjectConfigDir(), "memory.json");
78
- return path.join(getConfigDir(), "memory.json");
79
- }
80
- const memoryFileCache = new Map();
81
- function parseMemoryItem(value) {
82
- if (typeof value !== "object" || value === null)
83
- return undefined;
84
- const rec = value;
85
- if (typeof rec.content !== "string")
86
- return undefined;
87
- const tags = Array.isArray(rec.tags) ? rec.tags.filter((t) => typeof t === "string") : [];
88
- const created = typeof rec.created === "string" ? rec.created : "";
89
- return { content: rec.content, tags, created };
90
- }
91
- function backupCorrupt(file) {
92
- try {
93
- renameSync(file, `${file}.corrupt-${Date.now()}`);
94
- }
95
- catch { }
96
- }
97
- function rememberCache(file, memories) {
98
- try {
99
- const st = statSync(file);
100
- memoryFileCache.set(file, { mtimeMs: st.mtimeMs, size: st.size, memories });
101
- }
102
- catch {
103
- memoryFileCache.delete(file);
104
- }
105
- }
106
- export function loadMemories(scope = "global") {
107
- const file = getMemoryFile(scope);
108
- let st;
109
- try {
110
- st = statSync(file);
111
- }
112
- catch {
113
- memoryFileCache.delete(file);
114
- return [];
115
- }
116
- const cached = memoryFileCache.get(file);
117
- if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size)
118
- return cached.memories;
119
- let data;
120
- try {
121
- data = JSON.parse(readFileSync(file, "utf-8"));
122
- }
123
- catch {
124
- backupCorrupt(file);
125
- memoryFileCache.delete(file);
126
- return [];
127
- }
128
- if (!Array.isArray(data)) {
129
- backupCorrupt(file);
130
- memoryFileCache.delete(file);
131
- return [];
132
- }
133
- const memories = data.flatMap((item) => {
134
- const parsed = parseMemoryItem(item);
135
- return parsed ? [parsed] : [];
136
- });
137
- memoryFileCache.set(file, { mtimeMs: st.mtimeMs, size: st.size, memories });
138
- return memories;
139
- }
140
- function saveMemories(memories, scope) {
141
- const file = getMemoryFile(scope);
142
- mkdirSync(path.dirname(file), { recursive: true });
143
- atomicWriteFileSync(file, JSON.stringify(memories, null, 2));
144
- rememberCache(file, memories);
145
- }
146
- export function addMemory(content, tags = [], scope = "global") {
147
- const memories = loadMemories(scope);
148
- const memory = {
149
- content,
150
- tags,
151
- created: new Date().toISOString(),
152
- };
153
- memories.push(memory);
154
- saveMemories(memories, scope);
155
- return memory;
156
- }
157
- export function deleteMemory(index, scope = "global") {
158
- const memories = loadMemories(scope);
159
- if (index < 0 || index >= memories.length)
160
- return false;
161
- memories.splice(index, 1);
162
- saveMemories(memories, scope);
163
- return true;
164
- }
165
- export function searchMemories(query, scope = "global") {
166
- const memories = loadMemories(scope);
167
- const lower = query.toLowerCase();
168
- return memories
169
- .map((m, i) => ({ ...m, index: i }))
170
- .filter((m) => m.content.toLowerCase().includes(lower) || m.tags.some((t) => t.toLowerCase().includes(lower)));
171
- }
172
- export function formatMemoryLine(memory, index, opts) {
173
- const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
174
- const date = opts?.date ? memory.created.split("T")[0] : "";
175
- const suffix = date ? ` (${date})` : "";
176
- return ` #${index + 1}: ${memory.content}${tags}${suffix}`;
177
- }
178
- function formatMemoryBlock(memories, heading) {
179
- if (memories.length === 0)
180
- return "";
181
- const start = Math.max(0, memories.length - MAX_INJECTED_MEMORIES);
182
- const items = memories
183
- .slice(start)
184
- .map((m, i) => ({ m, num: start + i + 1 }))
185
- .reverse()
186
- .map(({ m, num }) => {
187
- const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
188
- return ` ${num}. ${m.content}${tags}`;
189
- });
190
- const capped = memories.length > MAX_INJECTED_MEMORIES
191
- ? `\n(${memories.length - MAX_INJECTED_MEMORIES} older memories not shown — use memory_search to find them.)`
192
- : "";
193
- return [heading, ...items, capped].filter(Boolean).join("\n");
194
- }
195
- export function getMemorySystemPrompt(runOverride) {
196
- if (!isMemoryEnabled(runOverride))
197
- return "";
198
- const project = loadMemories("project");
199
- const global = loadMemories("global");
200
- if (project.length === 0 && global.length === 0)
201
- return "";
202
- const intro = [
203
- "## Memories",
204
- "Background facts from previous conversations. Use them to personalize replies. They are not pending tasks and not a request to act.",
205
- "Do not call APIs, run commands, or continue old work based on memories unless the current user message explicitly asks.",
206
- "Do not use remembered credentials or API keys unless this message asks you to perform that action.",
207
- "You can save new memories with the memory_save tool when the user tells you something worth remembering (preferences, project details, conventions, etc).",
208
- 'Use scope "project" for repo-specific facts and "global" for preferences that apply everywhere.',
209
- "",
210
- ];
211
- const sections = [
212
- formatMemoryBlock(project, "### This project"),
213
- formatMemoryBlock(global, project.length > 0 ? "### Global" : ""),
214
- ].filter((s) => s.length > 0);
215
- return [...intro, ...sections].join("\n");
216
- }
217
- export function getMemoryTools() {
218
- const memorySave = tool({
219
- description: "Save a memory for future conversations. Use this when the user shares preferences, project conventions, important context, or asks you to remember something. Memories persist across sessions and are injected as background reference, not as work to resume. Do not store secrets, API keys, or passwords. Use scope project for this repository, global for all projects.",
220
- inputSchema: jsonSchema({
221
- type: "object",
222
- properties: {
223
- content: { type: "string", description: "The information to remember" },
224
- tags: {
225
- type: "array",
226
- items: { type: "string" },
227
- description: "Optional tags for categorization (e.g. 'preference', 'project', 'convention')",
228
- },
229
- scope: {
230
- type: "string",
231
- enum: ["global", "project"],
232
- description: "Where to store the memory. Defaults to project when .min-agent exists, otherwise global.",
233
- },
234
- },
235
- required: ["content"],
236
- }),
237
- execute: async ({ content, tags, scope }) => {
238
- const resolved = resolveStore(scope);
239
- const memory = addMemory(content, tags ?? [], resolved);
240
- return `Saved ${resolved} memory: "${content}" (tags: ${memory.tags.length > 0 ? memory.tags.join(", ") : "none"})`;
241
- },
242
- });
243
- const memorySearch = tool({
244
- description: "Search through saved memories by keyword. Use this to recall previously saved information.",
245
- inputSchema: jsonSchema({
246
- type: "object",
247
- properties: {
248
- query: { type: "string", description: "Search keyword or phrase" },
249
- scope: {
250
- type: "string",
251
- enum: ["global", "project"],
252
- description: "Limit search to one store. Omit to search both.",
253
- },
254
- },
255
- required: ["query"],
256
- }),
257
- execute: async ({ query, scope }) => {
258
- const scopes = scope === "global" || scope === "project" ? [scope] : ["project", "global"];
259
- const lines = scopes.flatMap((s) => searchMemories(query, s).map((m) => {
260
- const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
261
- return `${s} #${m.index + 1}: ${m.content}${tags} (${m.created.split("T")[0]})`;
262
- }));
263
- if (lines.length === 0)
264
- return `No memories found matching "${query}"`;
265
- return lines.join("\n");
266
- },
267
- });
268
- const memoryDelete = tool({
269
- description: "Delete a memory by its number. Use memory_search first to find the index and scope.",
270
- inputSchema: jsonSchema({
271
- type: "object",
272
- properties: {
273
- index: { type: "number", description: "The memory number to delete (1-based)" },
274
- scope: {
275
- type: "string",
276
- enum: ["global", "project"],
277
- description: "Which store the number belongs to. Defaults to project when .min-agent exists, otherwise global.",
278
- },
279
- },
280
- required: ["index"],
281
- }),
282
- execute: async ({ index, scope }) => {
283
- const resolved = resolveStore(scope);
284
- const success = deleteMemory(index - 1, resolved);
285
- if (success)
286
- return `${resolved} memory #${index} deleted.`;
287
- return `${resolved} memory #${index} not found.`;
288
- },
289
- });
290
- return {
291
- memory_save: memorySave,
292
- memory_search: memorySearch,
293
- memory_delete: memoryDelete,
294
- };
295
- }
@@ -1,285 +0,0 @@
1
- import { readFileSync, mkdirSync, existsSync } from "fs";
2
- import path from "path";
3
- import { atomicWriteFileSync } from "./tools/atomic-file.js";
4
- import { getConfigDir, parseThinkingEffort } from "./config.js";
5
- const DEFAULT_LONAE_BASE = "https://models.lonae.com";
6
- const CACHE_TTL = 7 * 24 * 60 * 60 * 1000;
7
- const CATALOG_ENTRY_VERSION = 3;
8
- const NEGATIVE_TTL = 60 * 60 * 1000;
9
- const FETCH_TIMEOUT_MS = 10000;
10
- const memoryCache = new Map();
11
- const inFlight = new Map();
12
- const negativeCache = new Map();
13
- export function lonaeBaseUrl() {
14
- const raw = process.env.MIN_AGENT_MODELS_API_URL?.trim();
15
- if (!raw)
16
- return DEFAULT_LONAE_BASE;
17
- return raw.replace(/\/$/, "");
18
- }
19
- function cacheFile() {
20
- return path.join(getConfigDir(), "model-catalog-cache.json");
21
- }
22
- function loadDiskCache() {
23
- const file = cacheFile();
24
- if (!existsSync(file))
25
- return {};
26
- try {
27
- return JSON.parse(readFileSync(file, "utf-8"));
28
- }
29
- catch {
30
- return {};
31
- }
32
- }
33
- function saveDiskCache(cache) {
34
- const file = cacheFile();
35
- mkdirSync(path.dirname(file), { recursive: true });
36
- atomicWriteFileSync(file, JSON.stringify(cache));
37
- }
38
- export function clearModelCatalogCache() {
39
- memoryCache.clear();
40
- inFlight.clear();
41
- negativeCache.clear();
42
- }
43
- export function getCachedModelCatalog(modelId) {
44
- const memory = memoryCache.get(modelId);
45
- if (memory && memory.version === CATALOG_ENTRY_VERSION && Date.now() - memory.timestamp <= CACHE_TTL)
46
- return memory;
47
- const disk = loadDiskCache()[modelId];
48
- if (!disk || disk.version !== CATALOG_ENTRY_VERSION || Date.now() - disk.timestamp > CACHE_TTL)
49
- return null;
50
- memoryCache.set(modelId, disk);
51
- return disk;
52
- }
53
- function setCache(modelId, entry) {
54
- memoryCache.set(modelId, entry);
55
- const cache = loadDiskCache();
56
- cache[modelId] = entry;
57
- saveDiskCache(cache);
58
- }
59
- function isRecord(value) {
60
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
61
- }
62
- export function parseReasoningOptions(raw) {
63
- if (!Array.isArray(raw))
64
- return { toggle: false, efforts: [] };
65
- let toggle = false;
66
- const efforts = new Set();
67
- for (const item of raw) {
68
- if (!isRecord(item))
69
- continue;
70
- if (item.type === "toggle")
71
- toggle = true;
72
- if (item.type === "effort" && Array.isArray(item.values)) {
73
- for (const value of item.values) {
74
- if (typeof value === "string" && value && value !== "default")
75
- efforts.add(value);
76
- }
77
- }
78
- }
79
- if (efforts.has("none"))
80
- toggle = true;
81
- return { toggle, efforts: [...efforts] };
82
- }
83
- export function pickListedModel(models, modelId) {
84
- const lower = modelId.toLowerCase();
85
- const exact = models.find((m) => m.id.toLowerCase() === lower);
86
- if (exact)
87
- return exact;
88
- const suffix = models.find((m) => {
89
- const id = m.id.toLowerCase();
90
- return id.endsWith(`/${lower}`) || lower.endsWith(`/${id}`);
91
- });
92
- if (suffix)
93
- return suffix;
94
- return models.find((m) => m.name?.toLowerCase() === lower);
95
- }
96
- export function matchOffering(offerings, baseURL) {
97
- if (!baseURL || offerings.length === 0)
98
- return undefined;
99
- let host = "";
100
- try {
101
- host = new URL(baseURL).hostname.toLowerCase();
102
- }
103
- catch {
104
- return undefined;
105
- }
106
- return offerings.find((o) => {
107
- const id = String(o.providerId ?? "")
108
- .toLowerCase()
109
- .replace(/_/g, "-");
110
- if (!id)
111
- return false;
112
- if (host.includes(id))
113
- return true;
114
- const slug = id.split("-")[0] ?? "";
115
- return slug.length >= 4 && host.includes(slug);
116
- });
117
- }
118
- const CANONICAL_EFFORTS = ["low", "medium", "high", "max"];
119
- function hasEffortLevel(parsed) {
120
- return parsed.efforts.some((raw) => {
121
- const canonical = parseThinkingEffort(raw);
122
- return Boolean(canonical && canonical !== "off");
123
- });
124
- }
125
- /** When the active host is unknown, keep levels a majority of offerings actually list. */
126
- export function majorityReasoning(offerings) {
127
- const voters = offerings
128
- .map((offering) => parseReasoningOptions(offering.reasoningOptions))
129
- .filter((parsed) => parsed.toggle || parsed.efforts.length > 0);
130
- if (voters.length === 0)
131
- return { toggle: false, efforts: [] };
132
- const effortVoters = voters.filter(hasEffortLevel);
133
- const toggleThreshold = voters.length / 2;
134
- const effortThreshold = effortVoters.length === 0 ? Number.POSITIVE_INFINITY : effortVoters.length / 2;
135
- let toggleVotes = 0;
136
- let xhighVotes = 0;
137
- const votes = new Map();
138
- for (const parsed of voters) {
139
- if (parsed.toggle || parsed.efforts.includes("none"))
140
- toggleVotes++;
141
- }
142
- for (const parsed of effortVoters) {
143
- const seen = new Set();
144
- for (const raw of parsed.efforts) {
145
- if (raw === "xhigh" || raw === "extra-high")
146
- xhighVotes++;
147
- const canonical = parseThinkingEffort(raw);
148
- if (!canonical || canonical === "off" || seen.has(canonical))
149
- continue;
150
- seen.add(canonical);
151
- votes.set(canonical, (votes.get(canonical) ?? 0) + 1);
152
- }
153
- }
154
- const toggle = toggleVotes >= toggleThreshold;
155
- const efforts = [];
156
- if (toggle)
157
- efforts.push("none");
158
- for (const key of CANONICAL_EFFORTS) {
159
- if ((votes.get(key) ?? 0) >= effortThreshold)
160
- efforts.push(key);
161
- }
162
- if (efforts.includes("max") && xhighVotes > 0)
163
- efforts.push("xhigh");
164
- return { toggle, efforts };
165
- }
166
- function contextFrom(limit) {
167
- const n = limit?.context;
168
- if (typeof n === "number" && Number.isFinite(n) && n >= 4096)
169
- return Math.floor(n);
170
- return undefined;
171
- }
172
- export function catalogFromLonaeModel(model, modelId, baseURL) {
173
- const offerings = model.offerings ?? [];
174
- const matched = matchOffering(offerings, baseURL);
175
- const reasoning = matched ? parseReasoningOptions(matched.reasoningOptions) : majorityReasoning(offerings);
176
- const contextWindow = contextFrom(matched?.limit) ?? contextFrom(model.limit);
177
- return {
178
- version: CATALOG_ENTRY_VERSION,
179
- modelId,
180
- lonaeId: model.id,
181
- ...(contextWindow != null ? { contextWindow } : {}),
182
- reasoning,
183
- timestamp: Date.now(),
184
- };
185
- }
186
- async function fetchJson(url) {
187
- try {
188
- const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
189
- if (!response.ok)
190
- return null;
191
- return await response.json();
192
- }
193
- catch {
194
- return null;
195
- }
196
- }
197
- function readModel(payload) {
198
- const root = isRecord(payload) ? payload : null;
199
- const data = root && isRecord(root.data) ? root.data : root;
200
- if (!data || typeof data.id !== "string")
201
- return null;
202
- const limit = isRecord(data.limit)
203
- ? { context: typeof data.limit.context === "number" ? data.limit.context : undefined }
204
- : undefined;
205
- const offerings = Array.isArray(data.offerings)
206
- ? data.offerings.filter(isRecord).map((o) => ({
207
- providerId: typeof o.providerId === "string" ? o.providerId : undefined,
208
- limit: isRecord(o.limit)
209
- ? { context: typeof o.limit.context === "number" ? o.limit.context : undefined }
210
- : undefined,
211
- reasoningOptions: o.reasoningOptions,
212
- }))
213
- : [];
214
- return {
215
- id: data.id,
216
- limit,
217
- reasoning: data.reasoning === true,
218
- offerings,
219
- };
220
- }
221
- function readList(payload) {
222
- const root = isRecord(payload) ? payload : null;
223
- const data = root && Array.isArray(root.data) ? root.data : Array.isArray(payload) ? payload : [];
224
- return data.flatMap((item) => {
225
- if (!isRecord(item) || typeof item.id !== "string")
226
- return [];
227
- return [
228
- {
229
- id: item.id,
230
- name: typeof item.name === "string" ? item.name : undefined,
231
- context: typeof item.context === "number" ? item.context : undefined,
232
- },
233
- ];
234
- });
235
- }
236
- function modelPath(id) {
237
- return id
238
- .split("/")
239
- .map((part) => encodeURIComponent(part))
240
- .join("/");
241
- }
242
- async function fetchLonaeModel(modelId) {
243
- const base = lonaeBaseUrl();
244
- if (modelId.includes("/")) {
245
- const direct = readModel(await fetchJson(`${base}/api/v1/models/${modelPath(modelId)}`));
246
- if (direct)
247
- return direct;
248
- }
249
- const listed = readList(await fetchJson(`${base}/api/v1/models?q=${encodeURIComponent(modelId)}&page_size=20`));
250
- const match = pickListedModel(listed, modelId);
251
- if (!match)
252
- return null;
253
- return readModel(await fetchJson(`${base}/api/v1/models/${modelPath(match.id)}`));
254
- }
255
- /**
256
- * Resolve context window and thinking options from the Lonae catalog.
257
- * Successful lookups are cached in memory and on disk for 7 days.
258
- */
259
- export async function getModelCatalog(modelId, baseURL) {
260
- const cached = getCachedModelCatalog(modelId);
261
- if (cached)
262
- return cached;
263
- const negAt = negativeCache.get(modelId);
264
- if (negAt !== undefined && Date.now() - negAt <= NEGATIVE_TTL)
265
- return null;
266
- const pending = inFlight.get(modelId);
267
- if (pending)
268
- return pending;
269
- const probing = fetchLonaeModel(modelId).then((model) => {
270
- if (!model) {
271
- negativeCache.set(modelId, Date.now());
272
- return null;
273
- }
274
- const entry = catalogFromLonaeModel(model, modelId, baseURL);
275
- setCache(modelId, entry);
276
- return entry;
277
- });
278
- inFlight.set(modelId, probing);
279
- try {
280
- return await probing;
281
- }
282
- finally {
283
- inFlight.delete(modelId);
284
- }
285
- }
package/dist/output.js DELETED
@@ -1,76 +0,0 @@
1
- import { loadConfig, getActiveProvider } from "./config.js";
2
- import { summarizeToolCall, toolResultText, truncateDisplay } from "./tool-display.js";
3
- const COLORS = {
4
- reset: "\x1b[0m",
5
- dim: "\x1b[2m",
6
- bold: "\x1b[1m",
7
- cyan: "\x1b[36m",
8
- green: "\x1b[32m",
9
- yellow: "\x1b[33m",
10
- red: "\x1b[31m",
11
- magenta: "\x1b[35m",
12
- gray: "\x1b[90m",
13
- };
14
- export function printHeader(modelId) {
15
- const model = modelId ?? getActiveProvider(loadConfig())?.defaultModel ?? "unknown";
16
- console.log(`${COLORS.bold}🤖 min-agent${COLORS.reset} ${COLORS.dim}(${model})${COLORS.reset}`);
17
- }
18
- export function printDivider() {
19
- console.log(`${COLORS.dim}${"─".repeat(60)}${COLORS.reset}`);
20
- }
21
- export function printToolCall(name, input) {
22
- const argsStr = summarizeToolCall(name, input, 160);
23
- console.log(`\n${COLORS.yellow}⚡ ${name}${COLORS.reset} ${COLORS.dim}${argsStr}${COLORS.reset}`);
24
- }
25
- /** Console preview limits for a tool result (the full text goes to the model). */
26
- const RESULT_PREVIEW_MAX_LINES = 20;
27
- const RESULT_PREVIEW_MAX_CHARS = 1200;
28
- /**
29
- * Print a tool result preview. Lines are kept whole and both limits are
30
- * applied in one pass, so the "N more lines" count always matches what was
31
- * actually withheld.
32
- */
33
- export function printToolResult(_name, result, isError = false) {
34
- const text = toolResultText(result).replace(/\s+$/, "");
35
- const lines = text === "" ? [] : text.split("\n");
36
- const kept = [];
37
- let chars = 0;
38
- for (const line of lines) {
39
- if (kept.length >= RESULT_PREVIEW_MAX_LINES)
40
- break;
41
- if (chars + line.length > RESULT_PREVIEW_MAX_CHARS)
42
- break;
43
- kept.push(line);
44
- chars += line.length + 1;
45
- }
46
- // A single very long first line still deserves a preview.
47
- if (kept.length === 0 && lines.length > 0) {
48
- kept.push(truncateDisplay(lines[0], RESULT_PREVIEW_MAX_CHARS));
49
- }
50
- const hidden = lines.length - kept.length;
51
- const suffix = hidden > 0 ? ` (${hidden} more line${hidden === 1 ? "" : "s"})` : "";
52
- const marker = isError ? `${COLORS.red} ✗${COLORS.reset}` : `${COLORS.green} ✓${COLORS.reset}`;
53
- const body = kept.join(`\n${COLORS.dim} `);
54
- console.log(`${marker} ${COLORS.dim}${body}${suffix}${COLORS.reset}\n`);
55
- }
56
- export function printDone(steps, usage, contextWindow, contextTokens) {
57
- const input = usage?.inputTokens ?? 0;
58
- const output = usage?.outputTokens ?? 0;
59
- const total = input + output;
60
- const occupied = contextTokens !== undefined ? contextTokens : input;
61
- let contextInfo = "";
62
- if (occupied > 0 && contextWindow && contextWindow > 0) {
63
- const pct = Math.round((occupied / contextWindow) * 100);
64
- const bar = renderBar(pct);
65
- contextInfo = ` | Context: ${bar} ${pct}%`;
66
- }
67
- console.log(`${COLORS.dim}Done in ${steps} step(s) | Tokens: ${input} in / ${output} out / ${total} total${contextInfo}${COLORS.reset}`);
68
- }
69
- function renderBar(pct) {
70
- const width = 10;
71
- const safePct = Math.max(0, Math.min(100, pct));
72
- const filled = Math.round((safePct / 100) * width);
73
- const empty = width - filled;
74
- const color = pct >= 80 ? "\x1b[31m" : pct >= 50 ? "\x1b[33m" : "\x1b[32m";
75
- return `${color}${"█".repeat(filled)}${"░".repeat(empty)}\x1b[0m\x1b[90m`;
76
- }