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,173 +0,0 @@
1
- import { displayWidth, clustersOf } from "./text-width.js";
2
- export const PASTE_CHIP_BASE = 0xe000;
3
- export const SNIPPET_MAX_CHARS = 1_000_000;
4
- export const IMAGE_MAX_BYTES = 5 * 1024 * 1024;
5
- export const NOTICE_IMAGE_TOO_LARGE = "图片超过 5MB,未附加";
6
- export const NOTICE_SNIPPET_TOO_LARGE = "粘贴内容过大,未附加";
7
- const PUA_START = 0xe000;
8
- const PUA_END = 0xf8ff;
9
- const UNMAPPED = "�";
10
- export function createPasteDraft() {
11
- return {
12
- nextCodePoint: PASTE_CHIP_BASE,
13
- nextImageId: 1,
14
- nextSnippetId: 1,
15
- attachments: new Map(),
16
- };
17
- }
18
- export function resetPasteDraft() {
19
- return createPasteDraft();
20
- }
21
- export function imageLabel(id) {
22
- return `[图片 #${id}]`;
23
- }
24
- export function snippetLabel(id, lineCount) {
25
- return `[粘贴 #${id} · ${lineCount} 行]`;
26
- }
27
- export function normalizePastedText(raw) {
28
- const normalized = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
29
- return normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
30
- }
31
- export function applyPastedText(draft, raw) {
32
- const text = normalizePastedText(raw);
33
- if (text.length > SNIPPET_MAX_CHARS) {
34
- return { kind: "reject", notice: NOTICE_SNIPPET_TOO_LARGE, draft };
35
- }
36
- if (!text.includes("\n")) {
37
- return { kind: "text", text, draft };
38
- }
39
- const { char, draft: next } = allocChip(draft, {
40
- kind: "snippet",
41
- id: draft.nextSnippetId,
42
- text,
43
- lineCount: text.split("\n").length,
44
- });
45
- return { kind: "snippet", char, draft: next };
46
- }
47
- export function applyClipboardImage(draft, image) {
48
- if (image == null)
49
- return { kind: "none", draft };
50
- if (image.data.length > IMAGE_MAX_BYTES) {
51
- return { kind: "reject", notice: NOTICE_IMAGE_TOO_LARGE, draft };
52
- }
53
- const { char, draft: next } = allocChip(draft, {
54
- kind: "image",
55
- id: draft.nextImageId,
56
- data: image.data,
57
- mimeType: image.mimeType,
58
- });
59
- return { kind: "image", char, draft: next };
60
- }
61
- export function pruneAttachments(draft, value) {
62
- const present = new Set(Array.from(value));
63
- const attachments = new Map([...draft.attachments].filter(([char]) => present.has(char)));
64
- if (attachments.size === draft.attachments.size)
65
- return draft;
66
- return { ...draft, attachments };
67
- }
68
- export function expandDisplay(value, draft) {
69
- return Array.from(value)
70
- .map((char) => {
71
- const att = draft.attachments.get(char);
72
- if (att)
73
- return attachmentLabel(att);
74
- return isPrivateUse(char) ? UNMAPPED : char;
75
- })
76
- .join("");
77
- }
78
- export function expandContent(value, draft) {
79
- const parts = [];
80
- let textBuf = "";
81
- const flushText = () => {
82
- if (!textBuf)
83
- return;
84
- parts.push({ type: "text", text: textBuf });
85
- textBuf = "";
86
- };
87
- for (const char of Array.from(value)) {
88
- const att = draft.attachments.get(char);
89
- if (att?.kind === "image") {
90
- flushText();
91
- parts.push({ type: "image", image: att.data, mimeType: att.mimeType });
92
- continue;
93
- }
94
- if (att?.kind === "snippet") {
95
- flushText();
96
- parts.push({ type: "text", text: snippetBlock(att.id, att.text, parts) });
97
- continue;
98
- }
99
- textBuf += isPrivateUse(char) ? UNMAPPED : char;
100
- }
101
- flushText();
102
- if (!parts.some((part) => part.type === "image")) {
103
- return parts.map((part) => (part.type === "text" ? part.text : "")).join("");
104
- }
105
- return parts;
106
- }
107
- export function buildSubmittedPrompt(value, draft) {
108
- return { display: expandDisplay(value, draft), content: expandContent(value, draft) };
109
- }
110
- export function chipClusterWidth(cluster, draft, maxWidth) {
111
- const att = draft.attachments.get(cluster);
112
- if (!att)
113
- return undefined;
114
- return Math.min(displayWidth(attachmentLabel(att)), maxWidth);
115
- }
116
- export function paintedChip(cluster, draft, maxWidth) {
117
- const att = draft.attachments.get(cluster);
118
- if (!att)
119
- return undefined;
120
- return truncateToWidth(attachmentLabel(att), maxWidth);
121
- }
122
- export function paintChar(char, draft, maxWidth) {
123
- return paintedChip(char, draft, maxWidth) ?? (isPrivateUse(char) ? UNMAPPED : char);
124
- }
125
- function attachmentLabel(att) {
126
- return att.kind === "image" ? imageLabel(att.id) : snippetLabel(att.id, att.lineCount);
127
- }
128
- function snippetBlock(id, text, previous) {
129
- const body = `--- 粘贴 #${id} ---\n${text}\n--- 粘贴 #${id} ---`;
130
- if (previous.length === 0)
131
- return body;
132
- const last = previous[previous.length - 1];
133
- if (last?.type === "text" && last.text.endsWith("\n"))
134
- return body;
135
- return `\n${body}`;
136
- }
137
- function allocChip(draft, attachment) {
138
- const char = String.fromCodePoint(draft.nextCodePoint);
139
- const attachments = new Map(draft.attachments);
140
- attachments.set(char, attachment);
141
- return {
142
- char,
143
- draft: {
144
- nextCodePoint: draft.nextCodePoint + 1,
145
- nextImageId: attachment.kind === "image" ? draft.nextImageId + 1 : draft.nextImageId,
146
- nextSnippetId: attachment.kind === "snippet" ? draft.nextSnippetId + 1 : draft.nextSnippetId,
147
- attachments,
148
- },
149
- };
150
- }
151
- function isPrivateUse(char) {
152
- const cp = char.codePointAt(0);
153
- return cp !== undefined && cp >= PUA_START && cp <= PUA_END;
154
- }
155
- function truncateToWidth(text, maxWidth) {
156
- if (maxWidth < 1)
157
- return "";
158
- if (displayWidth(text) <= maxWidth)
159
- return text;
160
- const ellipsis = "…";
161
- const ellipsisW = displayWidth(ellipsis);
162
- let out = "";
163
- let width = 0;
164
- for (const { text: g, width: w } of clustersOf(text)) {
165
- if (width + w + ellipsisW > maxWidth)
166
- break;
167
- out += g;
168
- width += w;
169
- }
170
- out += ellipsis;
171
- width += ellipsisW;
172
- return width < maxWidth ? `${out}${" ".repeat(maxWidth - width)}` : out;
173
- }
@@ -1,24 +0,0 @@
1
- export function createPromptQueue() {
2
- let tail = Promise.resolve();
3
- let generation = 0;
4
- let size = 0;
5
- const settle = () => undefined;
6
- return {
7
- run(job, onCancelled) {
8
- const gen = generation;
9
- size++;
10
- const start = () => (gen !== generation ? Promise.resolve(onCancelled()) : job());
11
- const result = tail.then(start, start);
12
- tail = result.then(settle, settle);
13
- return result.finally(() => {
14
- size--;
15
- });
16
- },
17
- cancelAll() {
18
- generation++;
19
- },
20
- get size() {
21
- return size;
22
- },
23
- };
24
- }
@@ -1,232 +0,0 @@
1
- import { clustersOf, displayWidth } from "./text-width.js";
2
- const RESET = "\x1b[0m";
3
- const INVERSE_ON = "\x1b[7m";
4
- const INVERSE_OFF = "\x1b[27m";
5
- /** Remove SGR color/style codes from a rendered line. */
6
- export function stripAnsi(line) {
7
- return line.replace(/\x1b\[[0-9;]*m/g, "");
8
- }
9
- /**
10
- * Wrap the cells in [start, end) with inverse video, preserving any styles
11
- * already active. A style reset inside the selection would cancel the
12
- * highlight, so a fresh INVERSE_ON is re-emitted after it; the closing
13
- * INVERSE_OFF restores the caller's original attribute state.
14
- */
15
- export function applySelection(line, start, end) {
16
- if (end <= start)
17
- return line;
18
- let out = "";
19
- let col = 0;
20
- let inSel = false;
21
- for (const token of line.split(/(\x1b\[[0-9;]*m)/g)) {
22
- if (token.startsWith("\x1b[")) {
23
- if (inSel && token === RESET) {
24
- if (col < end)
25
- out += token + INVERSE_ON;
26
- else {
27
- out += INVERSE_OFF + token;
28
- inSel = false;
29
- }
30
- continue;
31
- }
32
- out += token;
33
- continue;
34
- }
35
- for (const { text: g, width: w } of clustersOf(token)) {
36
- const cellStart = col;
37
- const cellEnd = col + w;
38
- const selected = cellEnd > start && cellStart < end;
39
- if (selected && !inSel) {
40
- out += INVERSE_ON;
41
- inSel = true;
42
- }
43
- else if (!selected && inSel) {
44
- out += INVERSE_OFF;
45
- inSel = false;
46
- }
47
- out += g;
48
- col = cellEnd;
49
- }
50
- }
51
- if (inSel)
52
- out += INVERSE_OFF;
53
- return out;
54
- }
55
- /** The plain text of the cells in [start, end) of an ANSI line. */
56
- export function extractText(line, start, end) {
57
- if (end <= start)
58
- return "";
59
- let out = "";
60
- let col = 0;
61
- for (const { text: g, width: w } of clustersOf(cachedStrip(line))) {
62
- const cellEnd = col + w;
63
- if (cellEnd > start && col < end)
64
- out += g;
65
- col = cellEnd;
66
- if (col >= end)
67
- break;
68
- }
69
- return out;
70
- }
71
- const strippedCache = new Map();
72
- function cachedStrip(row) {
73
- const hit = strippedCache.get(row);
74
- if (hit !== undefined)
75
- return hit;
76
- const s = stripAnsi(row);
77
- if (strippedCache.size > 512)
78
- strippedCache.clear();
79
- strippedCache.set(row, s);
80
- return s;
81
- }
82
- export function textFromSelection(rows, kinds, ranges) {
83
- const parts = [];
84
- for (let i = 0; i < ranges.length; i++) {
85
- const range = ranges[i];
86
- const row = rows[i];
87
- if (!range || !row)
88
- continue;
89
- parts.push(stripCopyDecorations(extractText(row, range.start, range.end), kinds[i] ?? "plain"));
90
- }
91
- while (parts.length > 0 && parts[parts.length - 1] === "")
92
- parts.pop();
93
- return parts.join("\n");
94
- }
95
- export function stripCopyDecorations(line, kind) {
96
- if (kind === "user-head" && line.startsWith("> "))
97
- return line.slice(2);
98
- if (kind === "tool-head")
99
- return line.replace(/^⚡ .*?[▸▾] /, "");
100
- if (kind === "thinking-head")
101
- return line.replace(/^💭 .*?[▸▾] /, "");
102
- if ((kind === "tool-body" || kind === "thinking-body") && line.startsWith(" "))
103
- return line.slice(2);
104
- return line;
105
- }
106
- /** Visible prefix rendered in its own Ink Text (user `> `, tool `⚡ name ▸ `, thinking `💭 思考 ▸ `). */
107
- export function visualPrefix(line, role) {
108
- if (role === "user" && line.startsWith("> "))
109
- return "> ";
110
- if (role === "tool") {
111
- const m = line.match(/^⚡ .*?[▸▾] /);
112
- return m ? m[0] : "";
113
- }
114
- if (role === "thinking") {
115
- const m = line.match(/^💭 .*?[▸▾] /);
116
- return m ? m[0] : "";
117
- }
118
- return "";
119
- }
120
- /** Split a display-column range across a prefix of `at` cells. */
121
- export function splitRangeAt(range, at) {
122
- const head = range.start < at && range.end > 0 ? { start: range.start, end: Math.min(at, range.end) } : null;
123
- const tail = range.end > at ? { start: Math.max(0, range.start - at), end: range.end - at } : null;
124
- return {
125
- head: head && head.end > head.start ? head : null,
126
- tail: tail && tail.end > tail.start ? tail : null,
127
- };
128
- }
129
- export function wordRangeAt(line, col) {
130
- const cells = [];
131
- let x = 0;
132
- for (const { text: g, width: w } of clustersOf(line)) {
133
- cells.push({ ch: g, start: x, end: x + w });
134
- x += w;
135
- }
136
- const hit = cells.find((c) => col >= c.start && col < c.end);
137
- if (!hit || /\s/.test(hit.ch))
138
- return { start: col, end: col };
139
- let left = cells.indexOf(hit);
140
- let right = left;
141
- while (left > 0 && !/\s/.test(cells[left - 1].ch))
142
- left--;
143
- while (right < cells.length - 1 && !/\s/.test(cells[right + 1].ch))
144
- right++;
145
- return { start: cells[left].start, end: cells[right].end };
146
- }
147
- /**
148
- * Map a 1-based SGR mouse position to a cell in `rows`.
149
- * `padLeft` is the Ink paddingLeft of every message row (first text cell is
150
- * terminal column padLeft+1).
151
- */
152
- export function mouseToCell(rows, termRow, termCol, padLeft = 1) {
153
- const maxRow = Math.max(0, rows.length - 1);
154
- return {
155
- row: Math.min(maxRow, Math.max(0, termRow - 1)),
156
- col: Math.max(0, termCol - 1 - padLeft),
157
- };
158
- }
159
- /**
160
- * Distribute viewport ranges onto each message part's shown-line index.
161
- * The resulting arrays are sparse: missing indices mean that shown line is
162
- * not highlighted. Callers must not require `arr.length === shown.length`.
163
- */
164
- export function alignPartSelections(ranges, partMeta) {
165
- const map = new Map();
166
- for (let i = 0; i < ranges.length; i++) {
167
- const meta = partMeta[i];
168
- const range = ranges[i];
169
- if (!meta || !range || range.end <= range.start)
170
- continue;
171
- let arr = map.get(meta.part);
172
- if (!arr) {
173
- arr = [];
174
- map.set(meta.part, arr);
175
- }
176
- arr[meta.shown] = range;
177
- }
178
- return map;
179
- }
180
- /**
181
- * Flow-style selection over the message area. `rows` mirrors the rendered
182
- * rows (null = blank/chrome row, no text); anchor/current are 0-based
183
- * (row = index into `rows`, col = display column of the cell under the
184
- * pointer). Both endpoints are inclusive: dragging onto a character selects
185
- * it. Dragging down selects the anchor through end-of-line, full middle rows,
186
- * then start through current; dragging up is symmetric.
187
- */
188
- export function selectionRanges(rows, anchor, current) {
189
- const result = rows.map(() => null);
190
- const top = Math.min(anchor.row, current.row);
191
- const bottom = Math.max(anchor.row, current.row);
192
- for (let r = top; r <= bottom; r++) {
193
- const line = rows[r];
194
- if (r < 0 || r >= rows.length || line === null)
195
- continue;
196
- const width = displayWidth(cachedStrip(line));
197
- let start;
198
- let end;
199
- if (anchor.row === current.row) {
200
- start = Math.min(anchor.col, current.col);
201
- end = Math.max(anchor.col, current.col) + 1;
202
- }
203
- else if (r === anchor.row) {
204
- if (anchor.row < current.row) {
205
- start = anchor.col;
206
- end = width;
207
- }
208
- else {
209
- start = 0;
210
- end = anchor.col + 1;
211
- }
212
- }
213
- else if (r === current.row) {
214
- if (anchor.row < current.row) {
215
- start = 0;
216
- end = current.col + 1;
217
- }
218
- else {
219
- start = current.col;
220
- end = width;
221
- }
222
- }
223
- else {
224
- start = 0;
225
- end = width;
226
- }
227
- start = Math.max(0, start);
228
- if (end > start)
229
- result[r] = { start, end };
230
- }
231
- return result;
232
- }
@@ -1,28 +0,0 @@
1
- export function prepareSwitch(opts) {
2
- if (opts.running)
3
- return { ok: false, reason: "running" };
4
- if (opts.currentId === opts.targetId)
5
- return { ok: false, reason: "current" };
6
- if (!opts.exists)
7
- return { ok: false, reason: "missing" };
8
- return { ok: true };
9
- }
10
- export function switchFailMessage(reason) {
11
- if (reason === "running")
12
- return "请先等待当前回合结束或按 Esc 取消";
13
- if (reason === "current")
14
- return "已是当前会话";
15
- return "会话不存在";
16
- }
17
- export function prepareNew(opts) {
18
- if (opts.running)
19
- return { ok: false, reason: "running" };
20
- if (!opts.sessionId && opts.messageCount === 0)
21
- return { ok: false, reason: "already_new" };
22
- return { ok: true };
23
- }
24
- export function newFailMessage(reason) {
25
- if (reason === "running")
26
- return "请先等待当前回合结束或按 Esc 取消";
27
- return "已是新会话";
28
- }
@@ -1,129 +0,0 @@
1
- export const SLASH_COMMANDS = [
2
- { name: "new", description: "新建会话(保留当前)" },
3
- { name: "compact", description: "压缩上下文" },
4
- { name: "model", description: "选择/切换模型" },
5
- { name: "models", description: "列出可用模型" },
6
- { name: "provider", description: "查看/切换 Provider", argHint: "[name]" },
7
- { name: "memory", description: "查看/开关记忆,或保存一条", argHint: "[on|off|t] [--project]" },
8
- { name: "tokens", description: "显示上下文占用、累计用量与成本" },
9
- { name: "ctx", description: "选择本地模型上下文窗口" },
10
- { name: "budget", description: "查看/设置预算上限", argHint: "[n] [--project]" },
11
- { name: "permission", description: "查看/设置确认模式", argHint: "[ask|accept-edits|allow-all] [--project]" },
12
- { name: "think", description: "选择思考强度", aliases: ["thinking"] },
13
- { name: "sandbox", description: "查看/设置隔离", argHint: "[off|workspace|strict] [--project]" },
14
- { name: "undo", description: "撤销上一轮" },
15
- { name: "redo", description: "重发最后一条消息" },
16
- { name: "diff", description: "显示工作区未提交变更" },
17
- { name: "plan", description: "切换只读计划模式" },
18
- { name: "sessions", description: "列出/切换已保存会话", argHint: "[id]", aliases: ["resume"] },
19
- { name: "reload", description: "重新加载规则" },
20
- { name: "rename", description: "重命名当前会话", argHint: "<title>" },
21
- { name: "attach", description: "附加本地图片", argHint: "<path>", aliases: ["image"] },
22
- { name: "mcp", description: "显示 MCP 状态" },
23
- {
24
- name: "skills",
25
- description: "显示/启用/禁用 skills",
26
- argHint: "[list|info <name>|enable|disable <name> [--project]]",
27
- },
28
- { name: "path", description: "显示工作目录", aliases: ["pwd"] },
29
- { name: "paste", description: "粘贴剪贴板图片", argHint: "[t]" },
30
- { name: "help", description: "显示帮助" },
31
- { name: "exit", description: "退出", aliases: ["quit", "q"] },
32
- ];
33
- export function filterSlashCommands(input, commands = SLASH_COMMANDS) {
34
- const q = input.slice(1).trim().toLowerCase();
35
- if (q === "")
36
- return commands;
37
- return commands.filter((c) => c.name.startsWith(q) || (c.aliases ?? []).some((a) => a.startsWith(q)));
38
- }
39
- export function sessionSlashCommands(providerType, commands = SLASH_COMMANDS) {
40
- if (providerType === "ollama")
41
- return commands;
42
- return commands.filter((c) => c.name !== "ctx");
43
- }
44
- /** Keep `selectedIndex` on screen; the highlight stays on the last row once the list scrolls. */
45
- export function slashMenuWindow(items, selectedIndex, maxVisible) {
46
- const max = Number.isFinite(maxVisible) ? Math.max(1, Math.floor(maxVisible)) : Math.max(1, items.length);
47
- const index = items.length === 0 ? 0 : Math.min(Math.max(0, selectedIndex), items.length - 1);
48
- if (items.length <= max) {
49
- return { start: 0, shown: [...items], extraAbove: 0, extraBelow: 0 };
50
- }
51
- const start = Math.max(0, Math.min(index - max + 1, items.length - max));
52
- const shown = items.slice(start, start + max);
53
- return {
54
- start,
55
- shown,
56
- extraAbove: start,
57
- extraBelow: items.length - (start + shown.length),
58
- };
59
- }
60
- /** True for submitted input that is a known slash command (including aliases). */
61
- export function isKnownSlashCommandInput(text, commands = SLASH_COMMANDS) {
62
- if (!text.startsWith("/"))
63
- return false;
64
- const name = text.slice(1).trim().split(/\s/, 1)[0]?.toLowerCase() ?? "";
65
- if (!name)
66
- return true;
67
- return commands.some((c) => c.name === name || (c.aliases ?? []).some((a) => a === name));
68
- }
69
- const argProviders = new Map();
70
- /** Registered by the app at startup so this module stays free of app imports. */
71
- export function setSlashArgProvider(command, provider) {
72
- argProviders.set(command, provider);
73
- }
74
- export function clearSlashArgProviders() {
75
- argProviders.clear();
76
- }
77
- function splitArgs(input) {
78
- const out = [];
79
- let cur = "";
80
- let q = null;
81
- for (const ch of input) {
82
- if (q) {
83
- if (ch === q)
84
- q = null;
85
- else
86
- cur += ch;
87
- }
88
- else if (ch === '"' || ch === "'")
89
- q = ch;
90
- else if (/\s/.test(ch)) {
91
- if (cur) {
92
- out.push(cur);
93
- cur = "";
94
- }
95
- }
96
- else
97
- cur += ch;
98
- }
99
- if (cur)
100
- out.push(cur);
101
- return out;
102
- }
103
- /** Null while the command name itself is still being typed (no space yet). */
104
- export function parseSlashArgs(value) {
105
- if (!value.startsWith("/"))
106
- return null;
107
- const raw = value.slice(1);
108
- if (!/\s/.test(raw))
109
- return null;
110
- const parts = splitArgs(raw.trim());
111
- const command = parts[0] ?? "";
112
- const rest = parts.slice(1);
113
- const partial = /\s$/.test(raw) ? "" : (rest.pop() ?? "");
114
- return { command, tokens: rest, partial };
115
- }
116
- export function getSlashArgOptions(value) {
117
- const ctx = parseSlashArgs(value);
118
- if (!ctx)
119
- return [];
120
- const provider = argProviders.get(ctx.command);
121
- if (!provider)
122
- return [];
123
- const prefix = ctx.partial.toLowerCase();
124
- return provider(ctx.tokens, ctx.partial).filter((o) => o.value.toLowerCase().startsWith(prefix));
125
- }
126
- /** Replace the token being typed with `option`, leaving a trailing space. */
127
- export function applySlashArgCompletion(value, option) {
128
- return /\s$/.test(value) ? `${value}${option} ` : `${value.replace(/\S+$/, option)} `;
129
- }