codemini-cli 0.1.18 → 0.2.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.
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: `run_command`, `read_file`, `write_file`
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: `locate`, `open_target`, `edit_target`
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 `locate -> open_target -> edit_target`
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
- - 默认工具极简:`run_command`、`read_file`、`write_file`
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
- - 为小模型补了结构化代码工具:`locate`、`open_target`、`edit_target`
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 原始输出干扰,新增了 `locate -> open_target -> edit_target` 这套结构化代码工具流
184
+ - 为了减少小模型被 shell 原始输出干扰,新增了 `grep/read -> edit` 这套结构化代码工具流
185
185
 
186
186
  ### Skill 加载位置
187
187
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.1.18",
3
+ "version": "0.2.0",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
@@ -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 `locate` first for repo search and candidate discovery
38
- - use `open_target` to inspect the smallest useful code block with edit metadata
39
- - use `edit_target` for minimal validated edits
40
- - use `search_code`, `read_block`, and `read_symbol_context` when lower-level structured context is needed
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 `locate`, then inspect with `open_target`, and only fall back to shell search such as `rg` when the structured tools are not enough.
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 `open_target`, `read_block`, and `read_symbol_context` before `read_file` when possible. Use `edit_target` for focused edits. Use `write_file` only for full-file writes. Avoid unnecessary tool calls and avoid rereading the same file without a reason.
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 `locate`
72
- - Inspect local context with `open_target`
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 `edit_target`
77
+ - Edit with `edit`
77
78
  - Verify
78
79
  - Summarize briefly
79
80
 
@@ -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 preview = String(obj.diff_preview || '')
30
- .split('\n')
31
- .slice(0, 3)
32
- .join('\n');
33
- return `${action} ${p} @L${line}${preview ? `\n${preview}` : ''}`;
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 === 'read_file' || name === 'write_file') {
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 === 'read_file') {
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 `${name}(${target}${suffix})`;
140
+ return `read(${target}${suffix})`;
110
141
  }
111
- return `${name}(${target})`;
142
+ return `write(${target})`;
112
143
  }
113
- if (name === 'run_command') {
144
+ if (name === 'run') {
114
145
  const command = trimInline(args?.command || '', 96);
115
- return command ? `${name}(${command})` : name;
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 displayName = formatToolDisplayName(call.name, args);
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(call.name)) {
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: call.name,
256
+ name: toolName,
230
257
  displayName,
231
258
  arguments: args
232
259
  });
@@ -235,7 +262,7 @@ export async function runAgentLoop({
235
262
  }
236
263
 
237
264
  if (!approved) {
238
- if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id });
265
+ if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id, arguments: args });
239
266
  const blockedMessage = {
240
267
  role: 'tool',
241
268
  tool_call_id: call.id,
@@ -247,6 +274,7 @@ export async function runAgentLoop({
247
274
  type: 'tool:result',
248
275
  name: displayName,
249
276
  id: call.id,
277
+ arguments: args,
250
278
  content: blockedMessage.content,
251
279
  blocked: true
252
280
  });
@@ -254,8 +282,8 @@ export async function runAgentLoop({
254
282
  continue;
255
283
  }
256
284
 
257
- if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id });
258
- const handler = toolHandlers[call.name];
285
+ if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id, arguments: args });
286
+ const handler = toolHandlers[toolName];
259
287
  if (!handler) {
260
288
  throw new Error(`Unknown tool: ${call.name}`);
261
289
  }
@@ -270,6 +298,7 @@ export async function runAgentLoop({
270
298
  type: 'tool:error',
271
299
  name: displayName,
272
300
  id: call.id,
301
+ arguments: args,
273
302
  durationMs,
274
303
  summary: trimInline(message, 120)
275
304
  });
@@ -285,6 +314,7 @@ export async function runAgentLoop({
285
314
  type: 'tool:result',
286
315
  name: displayName,
287
316
  id: call.id,
317
+ arguments: args,
288
318
  content: toolMessage.content,
289
319
  error: true
290
320
  });
@@ -297,6 +327,7 @@ export async function runAgentLoop({
297
327
  type: 'tool:end',
298
328
  name: displayName,
299
329
  id: call.id,
330
+ arguments: args,
300
331
  durationMs,
301
332
  summary: summarizeToolResult(toolResult)
302
333
  });
@@ -312,6 +343,7 @@ export async function runAgentLoop({
312
343
  type: 'tool:result',
313
344
  name: displayName,
314
345
  id: call.id,
346
+ arguments: args,
315
347
  content: toolMessage.content
316
348
  });
317
349
  }
@@ -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
- return {
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: effectiveMaxContextTokens(config)
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 || ['run_command', 'read_file', 'write_file'],
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' });
@@ -1359,6 +1377,9 @@ async function askModel({
1359
1377
  maxRetries: config.gateway.max_retries ?? 2,
1360
1378
  onTextDelta: (delta) => {
1361
1379
  if (onAgentEvent) onAgentEvent({ type: 'assistant:delta', text: delta });
1380
+ },
1381
+ onToolCallDelta: (toolCall) => {
1382
+ if (onAgentEvent) onAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
1362
1383
  }
1363
1384
  });
1364
1385
  }
@@ -1720,6 +1741,8 @@ export async function createChatRuntime({
1720
1741
  'sessions.retention_days',
1721
1742
  'shell.timeout_ms',
1722
1743
  'context.max_tokens',
1744
+ 'soul.preset',
1745
+ 'soul.custom_path',
1723
1746
  'policy.safe_mode',
1724
1747
  'policy.allow_dangerous_commands'
1725
1748
  ];
@@ -1851,9 +1874,21 @@ export async function createChatRuntime({
1851
1874
  const body = input.slice(1);
1852
1875
  const tokens = body.trim().split(/\s+/).filter(Boolean);
1853
1876
  const commandPart = tokens[0] || '';
1877
+ const commandHasSubcommands = new Set([
1878
+ 'config',
1879
+ 'compact',
1880
+ 'mode',
1881
+ 'tasks',
1882
+ 'checkpoint',
1883
+ 'plan',
1884
+ 'agents',
1885
+ 'history',
1886
+ 'debug'
1887
+ ]);
1854
1888
 
1855
1889
  const allCommandEntries = listCommandNames();
1856
1890
  const allCommands = allCommandEntries.map((c) => c.name);
1891
+ const exactCommand = Boolean(commandPart) && allCommands.includes(commandPart);
1857
1892
  for (const entry of allCommandEntries) {
1858
1893
  registerSuggestion(`/${entry.name}`, entry.description || '');
1859
1894
  }
@@ -1879,7 +1914,7 @@ export async function createChatRuntime({
1879
1914
  ));
1880
1915
  }
1881
1916
 
1882
- if (tokens.length === 1 && !hasTrailingSpace) {
1917
+ if (tokens.length === 1 && !hasTrailingSpace && !(exactCommand && commandHasSubcommands.has(commandPart))) {
1883
1918
  const direct = prioritizeByPreferredOrder(
1884
1919
  allCommands
1885
1920
  .filter((name) => name.startsWith(commandPart))
@@ -1891,25 +1926,26 @@ export async function createChatRuntime({
1891
1926
  }
1892
1927
 
1893
1928
  if (commandPart === 'config') {
1894
- if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
1895
- const sub = tokens[1] || '';
1929
+ const subcommand = tokens[1] || '';
1930
+ const subcommandIsExact = ['set', 'get', 'list', 'reset'].includes(subcommand);
1931
+
1932
+ if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace && !subcommandIsExact)) {
1896
1933
  return materializeSuggestions(prioritizeByPreferredOrder(
1897
1934
  ['set', 'get', 'list', 'reset']
1898
- .filter((s) => s.startsWith(sub))
1935
+ .filter((s) => s.startsWith(subcommand))
1899
1936
  .map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || 'config command').value),
1900
1937
  configSubcommandPriority
1901
1938
  ));
1902
1939
  }
1903
1940
 
1904
- const sub = tokens[1] || '';
1905
- if (sub === 'get') {
1906
- const keyPrefix = tokens[2] || '';
1941
+ if (subcommand === 'get') {
1942
+ const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
1907
1943
  return configKeyHints
1908
1944
  .filter((k) => k.startsWith(keyPrefix))
1909
1945
  .map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
1910
1946
  }
1911
- if (sub === 'set') {
1912
- const keyPrefix = tokens[2] || '';
1947
+ if (subcommand === 'set') {
1948
+ const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
1913
1949
  return configKeyHints
1914
1950
  .filter((k) => k.startsWith(keyPrefix))
1915
1951
  .map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
@@ -1920,6 +1956,11 @@ export async function createChatRuntime({
1920
1956
 
1921
1957
  if (commandPart === 'compact') {
1922
1958
  const joined = tokens.slice(1).join(' ');
1959
+ if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
1960
+ return compactOptions
1961
+ .filter((opt) => opt.startsWith(joined) || joined === '')
1962
+ .map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
1963
+ }
1923
1964
  return compactOptions
1924
1965
  .filter((opt) => opt.includes(joined) || joined === '')
1925
1966
  .map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
@@ -1952,6 +1993,10 @@ export async function createChatRuntime({
1952
1993
  if (commandPart === 'checkpoint') {
1953
1994
  if (tokens.length <= 2 && !hasTrailingSpace) {
1954
1995
  const sub = tokens[1] || '';
1996
+ if (sub === 'list') {
1997
+ return ['--all']
1998
+ .map((v) => registerSuggestion(`/checkpoint list ${v}`, 'checkpoint command'));
1999
+ }
1955
2000
  return ['create', 'list', 'load']
1956
2001
  .filter((s) => s.startsWith(sub))
1957
2002
  .map((s) => registerSuggestion(`/checkpoint ${s}`, 'checkpoint command'));
@@ -1987,6 +2032,10 @@ export async function createChatRuntime({
1987
2032
  if (commandPart === 'agents') {
1988
2033
  if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
1989
2034
  const sub = tokens[1] || '';
2035
+ if (sub === 'run') {
2036
+ return ['planner', 'coder', 'reviewer', 'tester']
2037
+ .map((r) => registerSuggestion(`/agents run ${r} `, 'sub-agent command'));
2038
+ }
1990
2039
  return ['list', 'run']
1991
2040
  .filter((s) => s.startsWith(sub))
1992
2041
  .map((s) => registerSuggestion(`/agents ${s}`, 'sub-agent command'));
@@ -2001,13 +2050,22 @@ export async function createChatRuntime({
2001
2050
  }
2002
2051
 
2003
2052
  if (commandPart === 'history') {
2053
+ const sub = tokens[1] || '';
2004
2054
  if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
2005
- const sub = tokens[1] || '';
2055
+ if (sub === 'resume') {
2056
+ const dynamic = historySessionCache
2057
+ .filter((session) => String(session.id || '').startsWith(''))
2058
+ .map((session) => ({
2059
+ value: `/history resume ${session.id}`,
2060
+ display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
2061
+ description: 'resume a saved session'
2062
+ }));
2063
+ if (dynamic.length > 0) return dynamic;
2064
+ }
2006
2065
  return ['list', 'current', 'resume']
2007
2066
  .filter((s) => s.startsWith(sub))
2008
- .map((s) => `/history ${s}`);
2067
+ .map((s) => registerSuggestion(`/history ${s}`, 'history command'));
2009
2068
  }
2010
- const sub = tokens[1] || '';
2011
2069
  if (sub === 'resume') {
2012
2070
  const idPrefix = tokens[2] || '';
2013
2071
  const dynamic = historySessionCache
@@ -2025,7 +2083,15 @@ export async function createChatRuntime({
2025
2083
 
2026
2084
  if (commandPart === 'debug') {
2027
2085
  const sub = tokens[1] || '';
2028
- if (!sub) return debugTemplates;
2086
+ if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
2087
+ if (sub === 'keys') {
2088
+ return ['on', 'off', 'status']
2089
+ .map((v) => registerSuggestion(`/debug keys ${v}`, 'keyboard debug command'));
2090
+ }
2091
+ return ['keys']
2092
+ .filter((s) => s.startsWith(sub))
2093
+ .map((s) => registerSuggestion(`/debug ${s}`, 'debug command'));
2094
+ }
2029
2095
  if (sub === 'keys') {
2030
2096
  const action = tokens[2] || '';
2031
2097
  return ['on', 'off', 'status']
@@ -2609,6 +2675,13 @@ export async function createChatRuntime({
2609
2675
  }
2610
2676
 
2611
2677
  const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
2678
+ const selectedAutoSkills = selectAutoSkillNames(expandedText).filter((name) => isSkillEnabled(config, name));
2679
+ if (selectedAutoSkills.length > 0 && onAgentEvent) {
2680
+ onAgentEvent({
2681
+ type: 'skill:auto',
2682
+ names: selectedAutoSkills
2683
+ });
2684
+ }
2612
2685
  const routedSystemPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
2613
2686
  const result = await askModel({
2614
2687
  text: expandedText,
@@ -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 locate and open_target 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 locate and open_target first. If you need shell fallback, use allowed search and context commands such as rg, find, grep, sed, cat, or ls.';
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 locate, open_target, and edit_target 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.';
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 locate, open_target, and edit_target first. If you need shell fallback, use allowed shell commands for search and local context such as rg, find, grep, sed, cat, or ls.';
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()) {
@@ -34,26 +34,20 @@ const DEFAULT_CONFIG = {
34
34
  execution: {
35
35
  mode: 'auto',
36
36
  always_allow_tools: [
37
- 'locate',
38
- 'open_target',
39
- 'edit_target',
40
- 'search_code',
41
- 'read_block',
42
- 'read_symbol_context',
43
- 'validate_edit',
44
- 'replace_block',
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
- 'locate',
138
- 'open_target',
139
- 'edit_target',
140
- 'search_code',
141
- 'read_block',
142
- 'read_symbol_context',
143
- 'validate_edit',
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
  );
@@ -205,6 +205,7 @@ export async function createChatCompletionStream({
205
205
  temperature = 0.2,
206
206
  tools,
207
207
  onTextDelta,
208
+ onToolCallDelta,
208
209
  timeoutMs = 90000,
209
210
  maxRetries = 2
210
211
  }) {
@@ -248,6 +249,14 @@ export async function createChatCompletionStream({
248
249
  if (td.function?.name) current.name = `${current.name}${td.function.name}`;
249
250
  if (td.function?.arguments) current.arguments = `${current.arguments}${td.function.arguments}`;
250
251
  toolCallsByIndex.set(idx, current);
252
+ if (onToolCallDelta) {
253
+ onToolCallDelta({
254
+ index: idx,
255
+ id: current.id || `tc-${idx + 1}`,
256
+ name: current.name,
257
+ arguments: current.arguments || '{}'
258
+ });
259
+ }
251
260
  }
252
261
  }
253
262
 
@@ -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 the high-level structured workflow first: use locate to find candidates, open_target to inspect the smallest useful block and receive edit metadata, and edit_target to apply minimal edits. When you need lower-level control, use search_code, read_block, read_symbol_context, validate_edit, replace_block, replace_text, insert_before, insert_after, and generate_diff. Use start_service, list_services, get_service_status, get_service_logs, and stop_service for long-running servers or watchers. Use run_command only for one-shot commands that should exit on their own. Use read_file only when structured reads are not enough. Use write_file only for full-file writes and always provide a concrete file path, not a directory. For existing code files, prefer locate -> open_target -> edit_target and only use write_file with full_file_rewrite=true when a whole-file rewrite is truly intended. Avoid unnecessary tool calls.`;
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 like install, build, test, or other finite tasks. Classify frontend, backend, database, and Docker work carefully: use run for finite commands, and use start_service, list_services, get_service_status, get_service_logs, and stop_service for long-running servers, watchers, and dev processes. 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. 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
  }