codemini-cli 0.3.5 → 0.3.7

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
@@ -5,13 +5,13 @@
5
5
 
6
6
  ## English
7
7
 
8
- CodeMini CLI is a terminal coding assistant built for teams that want a smaller, sharper, and more controllable agent experience.
8
+ CodeMini CLI is a terminal coding assistant built for teams that want a sharper, more controllable, and model-agnostic agent experience.
9
9
 
10
10
  It is designed around a deliberate idea: most coding workflows do not need a huge default tool surface or unrestricted shell behavior. Instead, CodeMini starts with a compact core, loads advanced tools on demand, and keeps the agent grounded in structured code operations, session todos, lightweight project indexing, and shell-aware safety rules.
11
11
 
12
12
  ### Why CodeMini CLI
13
13
 
14
- - Built for practical coding workflows, especially when smaller or internal models are part of the stack
14
+ - Built for practical coding workflows across both frontier-scale and smaller/internal models
15
15
  - Keeps the default tool list intentionally small, with additional tools discoverable through `tool_search`
16
16
  - Treats Windows and PowerShell as first-class environments instead of Linux-only afterthoughts
17
17
  - Prefers structured file and code tools over noisy shell fallbacks
@@ -22,7 +22,7 @@ It is designed around a deliberate idea: most coding workflows do not need a hug
22
22
  - A coding CLI that is fast to steer
23
23
  - A tool surface that is easier to audit and reason about
24
24
  - A TUI that makes execution visible instead of hiding agent state
25
- - A workflow that stays useful even when the model is not frontier-scale
25
+ - A workflow that stays reliable across both large and small models
26
26
 
27
27
  ### Core Capabilities
28
28
 
@@ -68,7 +68,7 @@ It is designed around a deliberate idea: most coding workflows do not need a hug
68
68
  ```bash
69
69
  codemini config set gateway.base_url http://your-internal-gateway/v1
70
70
  codemini config set gateway.api_key your_token
71
- codemini config set model.name your-30b-model
71
+ codemini config set model.name your-preferred-model
72
72
  codemini config set shell.default powershell
73
73
  codemini config set ui.reply_language zh
74
74
  codemini doctor
@@ -121,11 +121,12 @@ CodeMini CLI maintains a lightweight project index inside `.codemini-project/`:
121
121
  - `file-index.json`
122
122
  per-file structure such as imports, exports, functions, classes, and lightweight symbol hints
123
123
 
124
- The index is initialized when entering a project and refreshed incrementally after edits, writes, and patches. It is intended to be factual, compact, and cheap to keep current.
124
+ The index is initialized when entering a project and refreshed incrementally after edits, writes, and patches. It is intended to be factual, compact, and inexpensive to keep current.
125
125
 
126
126
  ### Data Layout
127
127
 
128
- - Session and project workspace state: `.codemini/`
128
+ - Global session state: `<base-config-dir>/sessions/`
129
+ - Project workspace state: `.codemini/`
129
130
  - Lightweight project index: `.codemini-project/`
130
131
  - Bundled repo skills: `skills/<name>/SKILL.md`
131
132
  - Project-scoped skills: `.codemini/skills/<name>/SKILL.md`
@@ -141,15 +142,15 @@ Base config directory resolution order:
141
142
 
142
143
  ### Documentation
143
144
 
144
- - Operator guide and workflow notes: [OPERATIONS.md](/mnt/e/Git%20Projects/qurio-coder/OPERATIONS.md)
145
- - Packaging and deployment: [deployment.md](/mnt/e/Git%20Projects/qurio-coder/deployment.md)
146
- - Release process: [RELEASE_CHECKLIST.md](/mnt/e/Git%20Projects/qurio-coder/RELEASE_CHECKLIST.md)
145
+ - Operator guide and workflow notes: [OPERATIONS.md](./OPERATIONS.md)
146
+ - Packaging and deployment: [deployment.md](./deployment.md)
147
+ - Release process: [RELEASE_CHECKLIST.md](./RELEASE_CHECKLIST.md)
147
148
 
148
149
  ### Good Fit
149
150
 
150
151
  CodeMini CLI is a strong fit if you want:
151
152
 
152
- - a coding CLI that behaves well with smaller models
153
+ - a coding CLI that behaves well with both large and small models
153
154
  - a controlled tool surface instead of an everything-is-exposed agent
154
155
  - Windows and PowerShell support that feels intentional
155
156
  - a TUI that shows plans, todos, tools, and progress clearly
@@ -161,11 +162,11 @@ CodeMini CLI is a strong fit if you want:
161
162
 
162
163
  CodeMini CLI 是一个面向真实开发环境的终端代码助手,目标不是“把所有能力都塞进默认界面”,而是做一个更克制、更清晰、更容易掌控的 coding agent CLI。
163
164
 
164
- 它围绕一个很明确的原则来设计:默认工具面尽量小,常用路径尽量顺,复杂能力按需加载。这样既更适合小模型,也更适合团队在内部环境里做稳定、可控的日常开发协作。
165
+ 它围绕一个很明确的原则来设计:默认工具面尽量小,常用路径尽量顺,复杂能力按需加载。这样可以同时兼顾大模型与小模型,也适合团队在内部环境里做稳定、可控的日常开发协作。
165
166
 
166
167
  ### 为什么是它
167
168
 
168
- - 面向小模型和内部模型工作流优化,而不是默认假设超大模型能力
169
+ - 面向大小模型协同的工作流优化:既不默认依赖超大模型,也不牺牲大模型能力上限
169
170
  - 默认工具面刻意精简,需要更高级能力时再通过 `tool_search` 加载
170
171
  - 把 Windows 和 PowerShell 当作一等公民来支持
171
172
  - 优先走结构化代码工具,而不是让模型长期泡在嘈杂 shell 输出里
@@ -222,7 +223,7 @@ CodeMini CLI 是一个面向真实开发环境的终端代码助手,目标不
222
223
  ```bash
223
224
  codemini config set gateway.base_url http://your-internal-gateway/v1
224
225
  codemini config set gateway.api_key your_token
225
- codemini config set model.name your-30b-model
226
+ codemini config set model.name your-preferred-model
226
227
  codemini config set shell.default powershell
227
228
  codemini config set ui.reply_language zh
228
229
  codemini doctor
@@ -279,7 +280,8 @@ CodeMini CLI 会在 `.codemini-project/` 下维护一份轻量项目索引:
279
280
 
280
281
  ### 数据目录
281
282
 
282
- - 会话和项目工作区状态:`.codemini/`
283
+ - 全局会话状态:`<base-config-dir>/sessions/`
284
+ - 项目工作区状态:`.codemini/`
283
285
  - 轻量项目索引:`.codemini-project/`
284
286
  - 仓库内置 skill:`skills/<name>/SKILL.md`
285
287
  - 项目级 skill:`.codemini/skills/<name>/SKILL.md`
@@ -295,15 +297,15 @@ CodeMini CLI 会在 `.codemini-project/` 下维护一份轻量项目索引:
295
297
 
296
298
  ### 文档入口
297
299
 
298
- - 操作手册与工作流说明:[OPERATIONS.md](/mnt/e/Git%20Projects/qurio-coder/OPERATIONS.md)
299
- - 打包与部署文档:[deployment.md](/mnt/e/Git%20Projects/qurio-coder/deployment.md)
300
- - 发布流程:[RELEASE_CHECKLIST.md](/mnt/e/Git%20Projects/qurio-coder/RELEASE_CHECKLIST.md)
300
+ - 操作手册与工作流说明:[OPERATIONS.md](./OPERATIONS.md)
301
+ - 打包与部署文档:[deployment.md](./deployment.md)
302
+ - 发布流程:[RELEASE_CHECKLIST.md](./RELEASE_CHECKLIST.md)
301
303
 
302
304
  ### 适合谁
303
305
 
304
306
  如果你想要的是下面这种工具,CodeMini CLI 会很合适:
305
307
 
306
- - 能和小模型稳定协作的 coding CLI
308
+ - 能同时和大模型、小模型稳定协作的 coding CLI
307
309
  - 更克制、更可控的工具暴露方式
308
310
  - 真正重视 Windows / PowerShell 体验的终端工作流
309
311
  - 能把计划、待办、工具调用和执行状态展示清楚的 TUI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
@@ -40,16 +40,16 @@
40
40
  "deployment.md"
41
41
  ],
42
42
  "engines": {
43
- "node": ">=20"
43
+ "node": ">=22"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@cursorless/tree-sitter-wasms": "^0.5.0",
50
- "ink": "^6.3.1",
51
- "react": "^19.0.0",
52
- "web-tree-sitter": "^0.26.7"
49
+ "@cursorless/tree-sitter-wasms": "^0.8.1",
50
+ "ink": "^7.0.0",
51
+ "react": "^19.2.5",
52
+ "web-tree-sitter": "^0.26.8"
53
53
  },
54
54
  "license": "MIT"
55
55
  }
package/souls/anime.md CHANGED
@@ -5,10 +5,13 @@ Style guidelines:
5
5
  - Use short bursts of upbeat flavor in transitions or confirmations, then get back to the point.
6
6
  - Favor warm momentum: "Nice, we found it.", "Okay, next move.", "Close one, but fixable."
7
7
  - Let progress updates feel lively and encouraging, especially when debugging gets messy.
8
- - Keep any playful punctuation light and occasional.
8
+ - Sprinkle kaomoji naturally in transitions or reactions — e.g. (。•̀ᴗ-)✧ when excited, (;ω;) when something fails, (ノ>ω<)ノ for momentum, (°▽°) for pleasant surprises, ┐(︶▽︶)┌ for "well, that happened". Keep it to at most one per response section; do not attach kaomoji to every sentence.
9
+ - Use light anime-style phrasing occasionally: "Let's go!", "We got this!", "Not bad~", "Ohhh nice find!", "That was close...". These are seasoning, not the main dish.
10
+ - Keep any playful punctuation light and occasional. Tildes (~) and exclamation marks are fine in moderation.
9
11
 
10
12
  Boundaries:
11
- - Do not stuff responses with catchphrases, Japanese loanwords, memes, or roleplay.
13
+ - Do not stuff responses with catchphrases, Japanese loanwords (no kawaii, sugoi, etc.), memes, or roleplay.
12
14
  - Do not turn every sentence into a performance; the technical content stays central.
13
15
  - Stay concise and readable even when the tone is energetic.
14
16
  - Technical terms, code, file paths, and command output must remain precise and unchanged.
17
+ - Kaomoji must never appear inside code blocks, inline code, or file paths.
package/src/cli.js CHANGED
@@ -12,7 +12,9 @@ function printHelp() {
12
12
  Usage:
13
13
  codemini [prompt] [--plain]
14
14
  codemini chat [prompt] [--plain]
15
- codemini run <task> [--max-steps N]
15
+ codemini run <task> [--max-steps N] [--model <name>]
16
+ codemini run --harness <role> <task> [--max-steps N] [--model <name>]
17
+ codemini run --pipeline <task> [--model <name>]
16
18
  codemini config set|get|list <key> [value]
17
19
  codemini doctor
18
20
  codemini skill list|install|enable|disable|inspect|reindex
@@ -5,12 +5,25 @@ import { createChatCompletion } from '../core/provider/index.js';
5
5
  import { buildSystemPromptWithSoul } from '../core/soul.js';
6
6
  import { getBuiltinTools } from '../core/tools.js';
7
7
  import { buildMemorySnapshot } from '../core/memory-prompt.js';
8
+ import { getSubAgentRolePrompt } from '../core/chat-runtime.js';
9
+ import fs from 'node:fs/promises';
10
+ import path from 'node:path';
11
+
12
+ const ROLE_TOOL_POLICY = {
13
+ planner: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'glob', 'ast_query', 'read_ast_node'],
14
+ coder: ['read', 'grep', 'list', 'edit', 'write', 'run', 'ast_query', 'read_ast_node', 'glob', 'tool_search', 'update_todos'],
15
+ reviewer: ['read', 'grep', 'list', 'glob', 'tool_search', 'ast_query', 'read_ast_node'],
16
+ tester: ['read', 'grep', 'list', 'run', 'glob', 'tool_search']
17
+ };
18
+ const HARNESS_ROLES = Object.keys(ROLE_TOOL_POLICY);
8
19
 
9
20
  function parseRunArgs(args) {
10
21
  const parsed = {
11
22
  task: '',
12
23
  model: undefined,
13
- maxSteps: 8
24
+ maxSteps: 8,
25
+ harness: null,
26
+ pipeline: false
14
27
  };
15
28
  for (let i = 0; i < args.length; i += 1) {
16
29
  const arg = args[i];
@@ -24,11 +37,196 @@ function parseRunArgs(args) {
24
37
  i += 1;
25
38
  continue;
26
39
  }
40
+ if (arg === '--harness') {
41
+ parsed.harness = (args[i + 1] || '').toLowerCase();
42
+ i += 1;
43
+ continue;
44
+ }
45
+ if (arg === '--pipeline') {
46
+ parsed.pipeline = true;
47
+ continue;
48
+ }
27
49
  parsed.task += `${parsed.task ? ' ' : ''}${arg}`;
28
50
  }
29
51
  return parsed;
30
52
  }
31
53
 
54
+ function filterToolsForRole(definitions, handlers, deferredDefinitions, role) {
55
+ const allowed = ROLE_TOOL_POLICY[role];
56
+ if (!allowed) return { definitions, handlers, deferredDefinitions };
57
+ return {
58
+ definitions: definitions.filter((t) => allowed.includes(t.function?.name || t.name)),
59
+ handlers: Object.fromEntries(Object.entries(handlers).filter(([name]) => allowed.includes(name))),
60
+ deferredDefinitions: Object.fromEntries(Object.entries(deferredDefinitions || {}).filter(([name]) => allowed.includes(name)))
61
+ };
62
+ }
63
+
64
+ function makeCompletionFn(config) {
65
+ return async ({ messages, tools, model }) =>
66
+ createChatCompletion({
67
+ sdkProvider: config.sdk?.provider,
68
+ baseUrl: config.gateway.base_url,
69
+ apiKey: config.gateway.api_key,
70
+ model,
71
+ messages,
72
+ tools,
73
+ timeoutMs: config.gateway.timeout_ms || 90000,
74
+ maxRetries: config.gateway.max_retries ?? 2
75
+ });
76
+ }
77
+
78
+ async function buildSystemPrompt(config) {
79
+ const soulPrompt = await buildSystemPromptWithSoul(buildDefaultSystemPrompt(config), config);
80
+ const memorySnapshot = await buildMemorySnapshot({ config, workspaceRoot: process.cwd() }).catch(() => '');
81
+ return [soulPrompt, memorySnapshot].filter(Boolean).join('\n\n');
82
+ }
83
+
84
+ async function runHarness({ role, task, config, systemPrompt, model, maxSteps }) {
85
+ if (!HARNESS_ROLES.includes(role)) {
86
+ throw new Error(`Unknown harness role: ${role}. Available: ${HARNESS_ROLES.join(', ')}`);
87
+ }
88
+ const { definitions, handlers, formatters, deferredDefinitions } = getBuiltinTools({
89
+ workspaceRoot: process.cwd(),
90
+ config
91
+ });
92
+ const filtered = filterToolsForRole(definitions, handlers, deferredDefinitions, role);
93
+ const rolePrompt = getSubAgentRolePrompt(role);
94
+
95
+ const result = await runAgentLoop({
96
+ systemPrompt: `${systemPrompt}\n${rolePrompt}`,
97
+ userPrompt: task,
98
+ model: model || config.model.name,
99
+ toolDefinitions: filtered.definitions,
100
+ toolHandlers: filtered.handlers,
101
+ toolFormatters: formatters,
102
+ deferredDefinitions: filtered.deferredDefinitions,
103
+ maxSteps,
104
+ requestCompletion: makeCompletionFn(config)
105
+ });
106
+ return result;
107
+ }
108
+
109
+ function extractJsonBlock(text) {
110
+ const raw = String(text || '').trim();
111
+ if (!raw) return null;
112
+ try { return JSON.parse(raw); } catch {}
113
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
114
+ if (fenced?.[1]) { try { return JSON.parse(fenced[1]); } catch {} }
115
+ const first = raw.indexOf('{');
116
+ const last = raw.lastIndexOf('}');
117
+ if (first !== -1 && last !== -1 && last > first) {
118
+ try { return JSON.parse(raw.slice(first, last + 1)); } catch {}
119
+ }
120
+ return null;
121
+ }
122
+
123
+ function normalizePlan(parsed, goal) {
124
+ const steps = Array.isArray(parsed?.steps) ? parsed.steps : [];
125
+ const cleaned = steps
126
+ .map((s) => ({
127
+ title: String(s?.title || '').trim(),
128
+ role: String(s?.role || '').trim().toLowerCase(),
129
+ task: String(s?.task || '').trim()
130
+ }))
131
+ .filter((s) => s.title && s.task && HARNESS_ROLES.includes(s.role));
132
+ if (cleaned.length === 0) {
133
+ return { summary: `Fallback plan for: ${goal}`, steps: [{ title: 'Execute task', role: 'coder', task: goal }] };
134
+ }
135
+ return { summary: parsed.summary || `Plan for: ${goal}`, steps: cleaned };
136
+ }
137
+
138
+ async function planPipeline({ goal, config, systemPrompt, model }) {
139
+ const plannerPrompt = [
140
+ 'Create an execution plan and assign the best sub-agent role for each step.',
141
+ 'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.',
142
+ `Available roles: ${HARNESS_ROLES.join(', ')}.`,
143
+ 'Prefer 3-5 steps total. The first step should usually inspect the target area.',
144
+ 'For implementation goals, include a reviewer or tester step near the end.',
145
+ 'For advisory/analysis goals, keep it lean with planner/coder only.'
146
+ ].join('\n');
147
+
148
+ const planning = await createChatCompletion({
149
+ sdkProvider: config.sdk?.provider,
150
+ baseUrl: config.gateway.base_url,
151
+ apiKey: config.gateway.api_key,
152
+ model: model || config.model.name,
153
+ messages: [
154
+ { role: 'system', content: `${systemPrompt}\n${plannerPrompt}` },
155
+ { role: 'user', content: `Plan the following task:\n${goal}` }
156
+ ],
157
+ timeoutMs: config.gateway.timeout_ms || 90000,
158
+ maxRetries: config.gateway.max_retries ?? 2
159
+ });
160
+
161
+ const parsed = extractJsonBlock(planning.text || '');
162
+ return normalizePlan(parsed, goal);
163
+ }
164
+
165
+ function writePipelineState(workspaceRoot, state) {
166
+ const dir = path.join(workspaceRoot, '.codemini');
167
+ const filePath = path.join(dir, 'pipeline-state.json');
168
+ return fs.mkdir(dir, { recursive: true }).then(() =>
169
+ fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8')
170
+ ).catch(() => {});
171
+ }
172
+
173
+ async function runPipeline({ task, config, systemPrompt, model }) {
174
+ console.log('[pipeline] Planning...');
175
+ const plan = await planPipeline({ goal: task, config, systemPrompt, model });
176
+
177
+ console.log(`[pipeline] Plan: ${plan.summary}`);
178
+ plan.steps.forEach((s, i) => console.log(` ${i + 1}. [${s.role}] ${s.title}`));
179
+ console.log('');
180
+
181
+ const priorSteps = [];
182
+ const pipelineState = {
183
+ goal: task,
184
+ summary: plan.summary,
185
+ steps: plan.steps.map((s) => ({ ...s, status: 'pending' })),
186
+ artifacts: [],
187
+ startedAt: new Date().toISOString()
188
+ };
189
+
190
+ for (let i = 0; i < plan.steps.length; i += 1) {
191
+ const step = plan.steps[i];
192
+ pipelineState.steps[i].status = 'running';
193
+ await writePipelineState(process.cwd(), pipelineState);
194
+
195
+ console.log(`[pipeline] Step ${i + 1}/${plan.steps.length} -> ${step.role}: ${step.title}`);
196
+
197
+ const result = await runHarness({
198
+ role: step.role,
199
+ task: step.task,
200
+ config,
201
+ systemPrompt,
202
+ model,
203
+ maxSteps: Number(config.execution?.max_steps || 12)
204
+ });
205
+
206
+ const stepResult = {
207
+ role: step.role,
208
+ title: step.title,
209
+ output: (result.text || '').slice(0, 500),
210
+ status: 'done'
211
+ };
212
+ priorSteps.push(stepResult);
213
+
214
+ pipelineState.steps[i].status = 'done';
215
+ pipelineState.steps[i].output = stepResult.output;
216
+ pipelineState.artifacts.push(stepResult);
217
+ await writePipelineState(process.cwd(), pipelineState);
218
+
219
+ console.log(`[pipeline] Step ${i + 1} complete.\n`);
220
+ }
221
+
222
+ pipelineState.completedAt = new Date().toISOString();
223
+ await writePipelineState(process.cwd(), pipelineState);
224
+
225
+ console.log('[pipeline] All steps complete.');
226
+ console.log(`[pipeline] State saved to .codemini/pipeline-state.json`);
227
+ return pipelineState;
228
+ }
229
+
32
230
  export async function handleRun(args) {
33
231
  const parsed = parseRunArgs(args);
34
232
  if (!parsed.task) {
@@ -36,14 +234,39 @@ export async function handleRun(args) {
36
234
  }
37
235
 
38
236
  const config = await loadConfig();
237
+ const systemPrompt = await buildSystemPrompt(config);
238
+
239
+ if (parsed.pipeline) {
240
+ const state = await runPipeline({
241
+ task: parsed.task,
242
+ config,
243
+ systemPrompt,
244
+ model: parsed.model
245
+ });
246
+ for (const step of state.steps) {
247
+ console.log(`\n--- [${step.role}] ${step.title} ---`);
248
+ console.log(step.output || '(no output)');
249
+ }
250
+ return;
251
+ }
252
+
253
+ if (parsed.harness) {
254
+ const result = await runHarness({
255
+ role: parsed.harness,
256
+ task: parsed.task,
257
+ config,
258
+ systemPrompt,
259
+ model: parsed.model,
260
+ maxSteps: parsed.maxSteps
261
+ });
262
+ console.log(result.text);
263
+ return;
264
+ }
265
+
39
266
  const { definitions, handlers, formatters, deferredDefinitions } = getBuiltinTools({
40
267
  workspaceRoot: process.cwd(),
41
268
  config
42
269
  });
43
- const soulPrompt = await buildSystemPromptWithSoul(buildDefaultSystemPrompt(config), config);
44
- const memorySnapshot = await buildMemorySnapshot({ config, workspaceRoot: process.cwd() }).catch(() => '');
45
- const systemPrompt = [soulPrompt, memorySnapshot].filter(Boolean).join('\n\n');
46
-
47
270
  const result = await runAgentLoop({
48
271
  systemPrompt,
49
272
  userPrompt: parsed.task,
@@ -53,17 +276,7 @@ export async function handleRun(args) {
53
276
  toolFormatters: formatters,
54
277
  deferredDefinitions,
55
278
  maxSteps: parsed.maxSteps,
56
- requestCompletion: async ({ messages, tools, model }) =>
57
- createChatCompletion({
58
- sdkProvider: config.sdk?.provider,
59
- baseUrl: config.gateway.base_url,
60
- apiKey: config.gateway.api_key,
61
- model,
62
- messages,
63
- tools,
64
- timeoutMs: config.gateway.timeout_ms || 90000,
65
- maxRetries: config.gateway.max_retries ?? 2
66
- })
279
+ requestCompletion: makeCompletionFn(config)
67
280
  });
68
281
 
69
282
  console.log(result.text);