codemini-cli 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/package.json +1 -1
- package/skills/superpowers-lite/SKILL.md +10 -9
- package/src/cli.js +1 -1
- package/src/core/agent-loop.js +56 -29
- package/src/core/chat-runtime.js +79 -16
- package/src/core/command-policy.js +4 -4
- package/src/core/config-store.js +16 -29
- package/src/core/shell-profile.js +1 -1
- package/src/core/tools.js +541 -210
- package/src/tui/chat-app.js +281 -124
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ It is designed for teams that want a coding assistant that feels practical, cont
|
|
|
19
19
|
|
|
20
20
|
### Highlights
|
|
21
21
|
|
|
22
|
-
- Minimal default tools: `
|
|
22
|
+
- Minimal default tools: `run`, `read`, `write`
|
|
23
23
|
- Windows-aware shell profile with PowerShell-focused defaults
|
|
24
24
|
- Safe mode enabled by default
|
|
25
25
|
- Built-in lite skills for planning, execution, and collaboration
|
|
26
26
|
- Configurable reply language through `ui.reply_language` (`zh` / `en`)
|
|
27
27
|
- Richer slash completion with priority sorting, inline descriptions, and left/right paging
|
|
28
|
-
- Structured code tools for small models: `
|
|
28
|
+
- Structured code tools for small models: `grep`, `read`, `edit`
|
|
29
29
|
- More conservative `plan auto` acceptance checks with reviewer/tester goal checklists
|
|
30
30
|
- Tone presets through `soul`, without changing plans or code behavior
|
|
31
31
|
- Example soul presets include `professional`, `playful`, `anime`, `pirate`, `caveman`, and `ceo`
|
|
@@ -66,7 +66,7 @@ codemini skill list|install|enable|disable|inspect|reindex
|
|
|
66
66
|
- Slash completion now prioritizes important commands and config keys, shows short descriptions, and supports `←/→` page switching
|
|
67
67
|
- Ambiguous feature requests can pause for lightweight brainstorming first, and `/brainstorm <question>` gives an explicit way to compare options before coding
|
|
68
68
|
- `plan auto` now turns the original goal into an acceptance checklist, uses a lighter chain only for truly tiny tasks, and treats unmet checklist items as failure signals
|
|
69
|
-
- Structured code tools reduce shell-noise for small models by preferring `
|
|
69
|
+
- Structured code tools reduce shell-noise for small models by preferring `grep/read -> edit`
|
|
70
70
|
|
|
71
71
|
### Skill Loading
|
|
72
72
|
|
|
@@ -134,13 +134,13 @@ CodeMini CLI 是一个为小模型工作流优化过的代码助手 CLI,重点
|
|
|
134
134
|
|
|
135
135
|
### 主要特点
|
|
136
136
|
|
|
137
|
-
- 默认工具极简:`
|
|
137
|
+
- 默认工具极简:`run`、`read`、`write`
|
|
138
138
|
- 面向 Windows 的 PowerShell 默认配置
|
|
139
139
|
- safe mode 默认开启
|
|
140
140
|
- 内置 lite skills,覆盖规划、执行和协作
|
|
141
141
|
- 支持通过 `ui.reply_language` 配置回复语言,当前支持 `zh` / `en`
|
|
142
142
|
- slash 补全支持优先级排序、右侧简短说明和左右分页
|
|
143
|
-
- 为小模型补了结构化代码工具:`
|
|
143
|
+
- 为小模型补了结构化代码工具:`grep`、`read`、`edit`
|
|
144
144
|
- `plan auto` 会基于原始目标生成验收清单,并更保守地处理 reviewer/tester 结果
|
|
145
145
|
- `soul` 只影响语气,不影响计划和代码行为
|
|
146
146
|
- 可用的 `soul` 示例包括 `professional`、`playful`、`anime`、`pirate`、`caveman`、`ceo`
|
|
@@ -181,7 +181,7 @@ codemini skill list|install|enable|disable|inspect|reindex
|
|
|
181
181
|
- slash 补全会优先展示更重要的命令和配置项,显示简短说明,并支持 `←/→` 翻页
|
|
182
182
|
- 对于需求仍不明确的功能请求,CLI 会先偏向轻量 brainstorm;也可以显式使用 `/brainstorm <问题>` 先比较方案再决定是否编码
|
|
183
183
|
- `plan auto` 会先把原始目标展开成验收清单;只有真正很小的任务才会走轻量链路;如果 reviewer 或 tester 标记了未满足或未验证的验收项,就不会按成功处理
|
|
184
|
-
- 为了减少小模型被 shell 原始输出干扰,新增了 `
|
|
184
|
+
- 为了减少小模型被 shell 原始输出干扰,新增了 `grep/read -> edit` 这套结构化代码工具流
|
|
185
185
|
|
|
186
186
|
### Skill 加载位置
|
|
187
187
|
|
package/package.json
CHANGED
|
@@ -34,16 +34,17 @@ Routing:
|
|
|
34
34
|
- do not assume features, storage model, or scope unless the user already gave them
|
|
35
35
|
|
|
36
36
|
Tool order:
|
|
37
|
-
- prefer `
|
|
38
|
-
- use `
|
|
39
|
-
- use `
|
|
40
|
-
- use `
|
|
37
|
+
- prefer `grep` first for content search and candidate discovery
|
|
38
|
+
- use `read` to inspect the smallest useful code block
|
|
39
|
+
- use `edit` for minimal focused edits or direct whole-file rewrites when you already have the replacement content
|
|
40
|
+
- use `generate_diff` and `patch` for larger edits or when you already have a diff
|
|
41
|
+
- use `glob` and `list` when you need file or directory discovery
|
|
41
42
|
- use shell search such as `rg` only as a fallback when structured tools are not enough
|
|
42
43
|
|
|
43
44
|
Core rules:
|
|
44
45
|
|
|
45
46
|
1. Search first.
|
|
46
|
-
Prefer structured search before broad file reads. Start with `
|
|
47
|
+
Prefer structured search before broad file reads. Start with `grep`, then inspect with `read`, and only fall back to shell search such as `rg` when the structured tools are not enough.
|
|
47
48
|
|
|
48
49
|
2. Keep context tight.
|
|
49
50
|
Do not carry full conversation history into every step. Summarize, narrow scope, and work from the most recent relevant evidence.
|
|
@@ -62,18 +63,18 @@ If the requested behavior, scope, or acceptance is unclear, do not jump into imp
|
|
|
62
63
|
- clear enough to build -> proceed
|
|
63
64
|
|
|
64
65
|
5. Read and write with intent.
|
|
65
|
-
Use `
|
|
66
|
+
Use `read` before broad reads when possible. Use `edit` for focused edits or when you already have the complete replacement content. Use `generate_diff` and `patch` for larger changes. Use `write` only for creating new files or explicit whole-file writes. Avoid unnecessary tool calls and avoid rereading the same file without a reason.
|
|
66
67
|
|
|
67
68
|
6. Verify before claiming success.
|
|
68
69
|
Run the relevant test, check, or command before saying work is fixed or complete.
|
|
69
70
|
|
|
70
71
|
Default workflow:
|
|
71
|
-
- Search with `
|
|
72
|
-
- Inspect local context with `
|
|
72
|
+
- Search with `grep`
|
|
73
|
+
- Inspect local context with `read`
|
|
73
74
|
- If the request is unclear, first decide: ask one question, brainstorm, or proceed
|
|
74
75
|
- Plan the next smallest step
|
|
75
76
|
- Delegate if the work is independent
|
|
76
|
-
- Edit with `
|
|
77
|
+
- Edit with `edit`
|
|
77
78
|
- Verify
|
|
78
79
|
- Summarize briefly
|
|
79
80
|
|
package/src/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { handleConfig } from './commands/config.js';
|
|
|
4
4
|
import { handleDoctor } from './commands/doctor.js';
|
|
5
5
|
import { handleSkill } from './commands/skill.js';
|
|
6
6
|
|
|
7
|
-
const VERSION = '0.1.
|
|
7
|
+
const VERSION = '0.1.18';
|
|
8
8
|
|
|
9
9
|
function printHelp() {
|
|
10
10
|
console.log(`codemini ${VERSION}
|
package/src/core/agent-loop.js
CHANGED
|
@@ -26,11 +26,19 @@ function summarizeToolResult(result) {
|
|
|
26
26
|
const p = String(obj.path || '');
|
|
27
27
|
const action = String(obj.action || 'write');
|
|
28
28
|
const line = Number(obj.changed_line || 1);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const suffix =
|
|
30
|
+
action === 'delete'
|
|
31
|
+
? 'deleted'
|
|
32
|
+
: action === 'create'
|
|
33
|
+
? 'created'
|
|
34
|
+
: action === 'patch'
|
|
35
|
+
? 'patched'
|
|
36
|
+
: action === 'replace_block' || action === 'replace_text'
|
|
37
|
+
? 'edited'
|
|
38
|
+
: action === 'append'
|
|
39
|
+
? 'appended'
|
|
40
|
+
: 'updated';
|
|
41
|
+
return p ? `${suffix} ${p}${line > 0 ? ` @L${line}` : ''}` : suffix;
|
|
34
42
|
}
|
|
35
43
|
if ('path' in obj && 'phase' in obj) {
|
|
36
44
|
const phase = String(obj.phase || '');
|
|
@@ -79,6 +87,13 @@ function summarizeToolResult(result) {
|
|
|
79
87
|
const logs = Array.isArray(obj.recent_logs) ? trimInline(obj.recent_logs.slice(-1)[0] || '', 96) : '';
|
|
80
88
|
return `${taskId || 'service logs'}${logs ? `\n${logs}` : ''}`;
|
|
81
89
|
}
|
|
90
|
+
if ('files' in obj && Array.isArray(obj.files)) {
|
|
91
|
+
return `patched ${obj.files.length} file(s)`;
|
|
92
|
+
}
|
|
93
|
+
if ('diff' in obj && 'new_hash' in obj && 'path' in obj) {
|
|
94
|
+
const p = String(obj.path || '');
|
|
95
|
+
return p ? `diff preview for ${p}` : 'diff preview';
|
|
96
|
+
}
|
|
82
97
|
if ('created' in obj && Array.isArray(obj.created)) {
|
|
83
98
|
return `created ${obj.created.length} task(s)`;
|
|
84
99
|
}
|
|
@@ -98,21 +113,45 @@ function trimInline(value, maxLen = 72) {
|
|
|
98
113
|
return `${s.slice(0, maxLen - 3)}...`;
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
function normalizeToolCallName(name) {
|
|
117
|
+
return String(name || '').trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
101
120
|
function formatToolDisplayName(name, args) {
|
|
102
|
-
if (name === '
|
|
121
|
+
if (name === 'grep') {
|
|
122
|
+
const query = trimInline(args?.pattern || args?.query || args?.symbol || '', 96);
|
|
123
|
+
return query ? `grep("${query}")` : 'grep';
|
|
124
|
+
}
|
|
125
|
+
if (name === 'glob') {
|
|
126
|
+
const pattern = trimInline(args?.pattern || '', 96);
|
|
127
|
+
return pattern ? `glob("${pattern}")` : 'glob';
|
|
128
|
+
}
|
|
129
|
+
if (name === 'list') {
|
|
130
|
+
const target = trimInline(args?.path || '.', 96) || '.';
|
|
131
|
+
return `list(${target})`;
|
|
132
|
+
}
|
|
133
|
+
if (name === 'read' || name === 'write') {
|
|
103
134
|
const target = trimInline(args?.path || '.', 96) || '.';
|
|
104
|
-
if (name === '
|
|
135
|
+
if (name === 'read') {
|
|
105
136
|
const start = Number(args?.start_line);
|
|
106
137
|
const end = Number(args?.end_line);
|
|
107
138
|
const hasRange = Number.isFinite(start) && start > 0;
|
|
108
139
|
const suffix = hasRange ? `:${start}-${Number.isFinite(end) && end >= start ? end : start}` : '';
|
|
109
|
-
return
|
|
140
|
+
return `read(${target}${suffix})`;
|
|
110
141
|
}
|
|
111
|
-
return
|
|
142
|
+
return `write(${target})`;
|
|
112
143
|
}
|
|
113
|
-
if (name === '
|
|
144
|
+
if (name === 'run') {
|
|
114
145
|
const command = trimInline(args?.command || '', 96);
|
|
115
|
-
return command ?
|
|
146
|
+
return command ? `run(${command})` : name;
|
|
147
|
+
}
|
|
148
|
+
if (name === 'edit') {
|
|
149
|
+
const target = trimInline(args?.path || args?.file || '.', 96) || '.';
|
|
150
|
+
return `edit(${target})`;
|
|
151
|
+
}
|
|
152
|
+
if (name === 'patch') {
|
|
153
|
+
const target = trimInline(args?.path || args?.file || args?.patch || '', 96) || '.';
|
|
154
|
+
return `patch(${target})`;
|
|
116
155
|
}
|
|
117
156
|
if (name === 'start_service') {
|
|
118
157
|
const command = trimInline(args?.command || args?.cmd || '', 96);
|
|
@@ -125,20 +164,7 @@ function formatToolDisplayName(name, args) {
|
|
|
125
164
|
const taskId = trimInline(args?.task_id || args?.taskId || '', 96);
|
|
126
165
|
return taskId ? `${name}(${taskId})` : name;
|
|
127
166
|
}
|
|
128
|
-
if (
|
|
129
|
-
name === 'locate' ||
|
|
130
|
-
name === 'open_target' ||
|
|
131
|
-
name === 'edit_target' ||
|
|
132
|
-
name === 'search_code' ||
|
|
133
|
-
name === 'read_block' ||
|
|
134
|
-
name === 'read_symbol_context' ||
|
|
135
|
-
name === 'validate_edit' ||
|
|
136
|
-
name === 'replace_block' ||
|
|
137
|
-
name === 'replace_text' ||
|
|
138
|
-
name === 'insert_before' ||
|
|
139
|
-
name === 'insert_after' ||
|
|
140
|
-
name === 'generate_diff'
|
|
141
|
-
) {
|
|
167
|
+
if (name === 'read' || name === 'write' || name === 'run' || name === 'grep' || name === 'glob' || name === 'list' || name === 'edit' || name === 'patch' || name === 'generate_diff') {
|
|
142
168
|
const target = trimInline(args?.path || args?.query || args?.symbol || '', 96);
|
|
143
169
|
return target ? `${name}(${target})` : name;
|
|
144
170
|
}
|
|
@@ -218,15 +244,16 @@ export async function runAgentLoop({
|
|
|
218
244
|
|
|
219
245
|
for (const call of toolCalls) {
|
|
220
246
|
const args = safeJsonParse(call.arguments);
|
|
221
|
-
const
|
|
247
|
+
const toolName = normalizeToolCallName(call.name);
|
|
248
|
+
const displayName = formatToolDisplayName(toolName, args);
|
|
222
249
|
const startedAt = Date.now();
|
|
223
250
|
let approved = true;
|
|
224
|
-
if (executionMode === 'normal' && !alwaysAllowSet.has(
|
|
251
|
+
if (executionMode === 'normal' && !alwaysAllowSet.has(toolName)) {
|
|
225
252
|
approved = false;
|
|
226
253
|
if (typeof requestToolApproval === 'function') {
|
|
227
254
|
const decision = await requestToolApproval({
|
|
228
255
|
id: call.id,
|
|
229
|
-
name:
|
|
256
|
+
name: toolName,
|
|
230
257
|
displayName,
|
|
231
258
|
arguments: args
|
|
232
259
|
});
|
|
@@ -255,7 +282,7 @@ export async function runAgentLoop({
|
|
|
255
282
|
}
|
|
256
283
|
|
|
257
284
|
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id });
|
|
258
|
-
const handler = toolHandlers[
|
|
285
|
+
const handler = toolHandlers[toolName];
|
|
259
286
|
if (!handler) {
|
|
260
287
|
throw new Error(`Unknown tool: ${call.name}`);
|
|
261
288
|
}
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -94,6 +94,8 @@ function describeConfigKey(key, mode = 'set') {
|
|
|
94
94
|
'shell.default': 'default shell',
|
|
95
95
|
'shell.timeout_ms': 'shell timeout in milliseconds',
|
|
96
96
|
'context.max_tokens': 'context token budget',
|
|
97
|
+
'soul.preset': 'soul preset',
|
|
98
|
+
'soul.custom_path': 'custom soul prompt path',
|
|
97
99
|
'policy.safe_mode': 'safe mode switch',
|
|
98
100
|
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
99
101
|
};
|
|
@@ -1116,12 +1118,28 @@ function effectiveMaxContextTokens(config) {
|
|
|
1116
1118
|
}
|
|
1117
1119
|
|
|
1118
1120
|
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode }) {
|
|
1119
|
-
|
|
1121
|
+
const currentContextTokens = estimateMessagesTokens(currentSession?.messages || []);
|
|
1122
|
+
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
1123
|
+
const contextUsagePct = maxContextTokens > 0 ? Math.min(100, Math.max(0, (currentContextTokens / maxContextTokens) * 100)) : 0;
|
|
1124
|
+
const snapshot = {
|
|
1120
1125
|
sessionId: currentSession?.id || '',
|
|
1121
1126
|
mode: executionMode || config.execution?.mode || 'auto',
|
|
1122
1127
|
model: model || config.model?.name || '',
|
|
1123
|
-
maxContextTokens
|
|
1128
|
+
maxContextTokens
|
|
1124
1129
|
};
|
|
1130
|
+
Object.defineProperties(snapshot, {
|
|
1131
|
+
currentContextTokens: {
|
|
1132
|
+
value: currentContextTokens,
|
|
1133
|
+
enumerable: false,
|
|
1134
|
+
writable: false
|
|
1135
|
+
},
|
|
1136
|
+
contextUsagePct: {
|
|
1137
|
+
value: contextUsagePct,
|
|
1138
|
+
enumerable: false,
|
|
1139
|
+
writable: false
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
return snapshot;
|
|
1125
1143
|
}
|
|
1126
1144
|
|
|
1127
1145
|
function estimatePromptTokensForRequest(sessionMessages, userText = '') {
|
|
@@ -1345,7 +1363,7 @@ async function askModel({
|
|
|
1345
1363
|
onEvent: wrappedAgentEvent,
|
|
1346
1364
|
executionMode: executionMode || config.execution?.mode || 'auto',
|
|
1347
1365
|
alwaysAllowTools:
|
|
1348
|
-
alwaysAllowTools || config.execution?.always_allow_tools || ['
|
|
1366
|
+
alwaysAllowTools || config.execution?.always_allow_tools || ['run', 'read', 'write'],
|
|
1349
1367
|
toolResultMaxChars: config.context?.tool_result_max_chars || 12000,
|
|
1350
1368
|
requestCompletion: async ({ messages, tools, model: selectedModel }) => {
|
|
1351
1369
|
if (onAgentEvent) onAgentEvent({ type: 'assistant:start' });
|
|
@@ -1720,6 +1738,8 @@ export async function createChatRuntime({
|
|
|
1720
1738
|
'sessions.retention_days',
|
|
1721
1739
|
'shell.timeout_ms',
|
|
1722
1740
|
'context.max_tokens',
|
|
1741
|
+
'soul.preset',
|
|
1742
|
+
'soul.custom_path',
|
|
1723
1743
|
'policy.safe_mode',
|
|
1724
1744
|
'policy.allow_dangerous_commands'
|
|
1725
1745
|
];
|
|
@@ -1851,9 +1871,21 @@ export async function createChatRuntime({
|
|
|
1851
1871
|
const body = input.slice(1);
|
|
1852
1872
|
const tokens = body.trim().split(/\s+/).filter(Boolean);
|
|
1853
1873
|
const commandPart = tokens[0] || '';
|
|
1874
|
+
const commandHasSubcommands = new Set([
|
|
1875
|
+
'config',
|
|
1876
|
+
'compact',
|
|
1877
|
+
'mode',
|
|
1878
|
+
'tasks',
|
|
1879
|
+
'checkpoint',
|
|
1880
|
+
'plan',
|
|
1881
|
+
'agents',
|
|
1882
|
+
'history',
|
|
1883
|
+
'debug'
|
|
1884
|
+
]);
|
|
1854
1885
|
|
|
1855
1886
|
const allCommandEntries = listCommandNames();
|
|
1856
1887
|
const allCommands = allCommandEntries.map((c) => c.name);
|
|
1888
|
+
const exactCommand = Boolean(commandPart) && allCommands.includes(commandPart);
|
|
1857
1889
|
for (const entry of allCommandEntries) {
|
|
1858
1890
|
registerSuggestion(`/${entry.name}`, entry.description || '');
|
|
1859
1891
|
}
|
|
@@ -1879,7 +1911,7 @@ export async function createChatRuntime({
|
|
|
1879
1911
|
));
|
|
1880
1912
|
}
|
|
1881
1913
|
|
|
1882
|
-
if (tokens.length === 1 && !hasTrailingSpace) {
|
|
1914
|
+
if (tokens.length === 1 && !hasTrailingSpace && !(exactCommand && commandHasSubcommands.has(commandPart))) {
|
|
1883
1915
|
const direct = prioritizeByPreferredOrder(
|
|
1884
1916
|
allCommands
|
|
1885
1917
|
.filter((name) => name.startsWith(commandPart))
|
|
@@ -1891,25 +1923,26 @@ export async function createChatRuntime({
|
|
|
1891
1923
|
}
|
|
1892
1924
|
|
|
1893
1925
|
if (commandPart === 'config') {
|
|
1894
|
-
|
|
1895
|
-
|
|
1926
|
+
const subcommand = tokens[1] || '';
|
|
1927
|
+
const subcommandIsExact = ['set', 'get', 'list', 'reset'].includes(subcommand);
|
|
1928
|
+
|
|
1929
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace && !subcommandIsExact)) {
|
|
1896
1930
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
1897
1931
|
['set', 'get', 'list', 'reset']
|
|
1898
|
-
.filter((s) => s.startsWith(
|
|
1932
|
+
.filter((s) => s.startsWith(subcommand))
|
|
1899
1933
|
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || 'config command').value),
|
|
1900
1934
|
configSubcommandPriority
|
|
1901
1935
|
));
|
|
1902
1936
|
}
|
|
1903
1937
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
const keyPrefix = tokens[2] || '';
|
|
1938
|
+
if (subcommand === 'get') {
|
|
1939
|
+
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
1907
1940
|
return configKeyHints
|
|
1908
1941
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1909
1942
|
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
|
|
1910
1943
|
}
|
|
1911
|
-
if (
|
|
1912
|
-
const keyPrefix = tokens[2] || '';
|
|
1944
|
+
if (subcommand === 'set') {
|
|
1945
|
+
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
1913
1946
|
return configKeyHints
|
|
1914
1947
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1915
1948
|
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
|
|
@@ -1920,6 +1953,11 @@ export async function createChatRuntime({
|
|
|
1920
1953
|
|
|
1921
1954
|
if (commandPart === 'compact') {
|
|
1922
1955
|
const joined = tokens.slice(1).join(' ');
|
|
1956
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1957
|
+
return compactOptions
|
|
1958
|
+
.filter((opt) => opt.startsWith(joined) || joined === '')
|
|
1959
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
|
|
1960
|
+
}
|
|
1923
1961
|
return compactOptions
|
|
1924
1962
|
.filter((opt) => opt.includes(joined) || joined === '')
|
|
1925
1963
|
.map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
|
|
@@ -1952,6 +1990,10 @@ export async function createChatRuntime({
|
|
|
1952
1990
|
if (commandPart === 'checkpoint') {
|
|
1953
1991
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
1954
1992
|
const sub = tokens[1] || '';
|
|
1993
|
+
if (sub === 'list') {
|
|
1994
|
+
return ['--all']
|
|
1995
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, 'checkpoint command'));
|
|
1996
|
+
}
|
|
1955
1997
|
return ['create', 'list', 'load']
|
|
1956
1998
|
.filter((s) => s.startsWith(sub))
|
|
1957
1999
|
.map((s) => registerSuggestion(`/checkpoint ${s}`, 'checkpoint command'));
|
|
@@ -1987,6 +2029,10 @@ export async function createChatRuntime({
|
|
|
1987
2029
|
if (commandPart === 'agents') {
|
|
1988
2030
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1989
2031
|
const sub = tokens[1] || '';
|
|
2032
|
+
if (sub === 'run') {
|
|
2033
|
+
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2034
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, 'sub-agent command'));
|
|
2035
|
+
}
|
|
1990
2036
|
return ['list', 'run']
|
|
1991
2037
|
.filter((s) => s.startsWith(sub))
|
|
1992
2038
|
.map((s) => registerSuggestion(`/agents ${s}`, 'sub-agent command'));
|
|
@@ -2001,13 +2047,22 @@ export async function createChatRuntime({
|
|
|
2001
2047
|
}
|
|
2002
2048
|
|
|
2003
2049
|
if (commandPart === 'history') {
|
|
2050
|
+
const sub = tokens[1] || '';
|
|
2004
2051
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2005
|
-
|
|
2052
|
+
if (sub === 'resume') {
|
|
2053
|
+
const dynamic = historySessionCache
|
|
2054
|
+
.filter((session) => String(session.id || '').startsWith(''))
|
|
2055
|
+
.map((session) => ({
|
|
2056
|
+
value: `/history resume ${session.id}`,
|
|
2057
|
+
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2058
|
+
description: 'resume a saved session'
|
|
2059
|
+
}));
|
|
2060
|
+
if (dynamic.length > 0) return dynamic;
|
|
2061
|
+
}
|
|
2006
2062
|
return ['list', 'current', 'resume']
|
|
2007
2063
|
.filter((s) => s.startsWith(sub))
|
|
2008
|
-
.map((s) => `/history ${s}
|
|
2064
|
+
.map((s) => registerSuggestion(`/history ${s}`, 'history command'));
|
|
2009
2065
|
}
|
|
2010
|
-
const sub = tokens[1] || '';
|
|
2011
2066
|
if (sub === 'resume') {
|
|
2012
2067
|
const idPrefix = tokens[2] || '';
|
|
2013
2068
|
const dynamic = historySessionCache
|
|
@@ -2025,7 +2080,15 @@ export async function createChatRuntime({
|
|
|
2025
2080
|
|
|
2026
2081
|
if (commandPart === 'debug') {
|
|
2027
2082
|
const sub = tokens[1] || '';
|
|
2028
|
-
if (!
|
|
2083
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2084
|
+
if (sub === 'keys') {
|
|
2085
|
+
return ['on', 'off', 'status']
|
|
2086
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, 'keyboard debug command'));
|
|
2087
|
+
}
|
|
2088
|
+
return ['keys']
|
|
2089
|
+
.filter((s) => s.startsWith(sub))
|
|
2090
|
+
.map((s) => registerSuggestion(`/debug ${s}`, 'debug command'));
|
|
2091
|
+
}
|
|
2029
2092
|
if (sub === 'keys') {
|
|
2030
2093
|
const action = tokens[2] || '';
|
|
2031
2094
|
return ['on', 'off', 'status']
|
|
@@ -16,13 +16,13 @@ function suggestionForToken(token, config) {
|
|
|
16
16
|
const shell = String(config?.shell?.default || '').toLowerCase();
|
|
17
17
|
if (token === 'find' || token === 'grep') {
|
|
18
18
|
return shell === 'powershell'
|
|
19
|
-
? 'Prefer structured tools like
|
|
20
|
-
: 'Prefer structured tools like
|
|
19
|
+
? 'Prefer structured tools like grep, list, read, and edit first. If you need shell fallback, use allowed search and context commands such as Get-ChildItem, Select-String, Get-Content, or rg when available.'
|
|
20
|
+
: 'Prefer structured tools like grep, glob, list, read, and edit first. If you need shell fallback, use allowed search and context commands such as rg, find, grep, sed, cat, or ls.';
|
|
21
21
|
}
|
|
22
22
|
if (shell === 'powershell') {
|
|
23
|
-
return 'Prefer structured tools like
|
|
23
|
+
return 'Prefer structured tools like read, edit, write, grep, and list first. If you need shell fallback, use allowed shell commands for search and local context such as Get-ChildItem, Get-Content, Select-String, or rg when available.';
|
|
24
24
|
}
|
|
25
|
-
return 'Prefer structured tools like
|
|
25
|
+
return 'Prefer structured tools like read, edit, write, grep, glob, and list first. If you need shell fallback, use allowed shell commands for search and local context such as rg, find, grep, sed, cat, or ls.';
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function evaluateCommandPolicy(command, config, workspaceRoot = process.cwd()) {
|
package/src/core/config-store.js
CHANGED
|
@@ -34,26 +34,20 @@ const DEFAULT_CONFIG = {
|
|
|
34
34
|
execution: {
|
|
35
35
|
mode: 'auto',
|
|
36
36
|
always_allow_tools: [
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'replace_text',
|
|
46
|
-
'insert_before',
|
|
47
|
-
'insert_after',
|
|
37
|
+
'read',
|
|
38
|
+
'grep',
|
|
39
|
+
'glob',
|
|
40
|
+
'list',
|
|
41
|
+
'edit',
|
|
42
|
+
'write',
|
|
43
|
+
'run',
|
|
44
|
+
'patch',
|
|
48
45
|
'generate_diff',
|
|
49
46
|
'start_service',
|
|
50
47
|
'list_services',
|
|
51
48
|
'get_service_status',
|
|
52
49
|
'get_service_logs',
|
|
53
|
-
'stop_service'
|
|
54
|
-
'run_command',
|
|
55
|
-
'read_file',
|
|
56
|
-
'write_file'
|
|
50
|
+
'stop_service'
|
|
57
51
|
],
|
|
58
52
|
max_steps: 16
|
|
59
53
|
},
|
|
@@ -134,26 +128,19 @@ function normalizePolicyLists(config) {
|
|
|
134
128
|
: [];
|
|
135
129
|
next.execution.always_allow_tools = uniqueStrings(
|
|
136
130
|
[
|
|
137
|
-
'
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
'
|
|
143
|
-
'
|
|
144
|
-
'replace_block',
|
|
145
|
-
'replace_text',
|
|
146
|
-
'insert_before',
|
|
147
|
-
'insert_after',
|
|
131
|
+
'read',
|
|
132
|
+
'grep',
|
|
133
|
+
'glob',
|
|
134
|
+
'list',
|
|
135
|
+
'edit',
|
|
136
|
+
'write',
|
|
137
|
+
'run',
|
|
148
138
|
'generate_diff',
|
|
149
139
|
'start_service',
|
|
150
140
|
'list_services',
|
|
151
141
|
'get_service_status',
|
|
152
142
|
'get_service_logs',
|
|
153
143
|
'stop_service',
|
|
154
|
-
'run_command',
|
|
155
|
-
'read_file',
|
|
156
|
-
'write_file',
|
|
157
144
|
...rawTools
|
|
158
145
|
].filter((name) => String(name) !== 'list_files')
|
|
159
146
|
);
|
|
@@ -118,5 +118,5 @@ export function getEffectivePolicy(config) {
|
|
|
118
118
|
|
|
119
119
|
export function getShellSystemPrompt(value) {
|
|
120
120
|
const profile = getShellProfile(value);
|
|
121
|
-
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer
|
|
121
|
+
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer OpenCode-style primary tools first: use read to inspect files, grep to search file contents, glob to find files by pattern, list to inspect directories, edit to modify existing files, write to create or fully rewrite files when appropriate, patch to apply unified diffs, and run for one-shot shell commands. Treat edit as the default editing path for existing code. Internal low-level edit strategies such as target resolution, block replacement, exact text replacement, and anchored inserts are handled inside edit rather than exposed as separate tools. Use generate_diff when you need a structured preview of a proposed file change. Use start_service, list_services, get_service_status, get_service_logs, and stop_service for long-running servers or watchers. Use run only for one-shot commands that should exit on their own. For existing code files, prefer grep/read/edit and only use write with full_file_rewrite=true when a whole-file rewrite is truly intended. Avoid unnecessary tool calls.`;
|
|
122
122
|
}
|