codemini-cli 0.5.7 → 0.5.9

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/package.json CHANGED
@@ -1,67 +1,67 @@
1
- {
2
- "name": "codemini-cli",
3
- "version": "0.5.7",
4
- "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
- "keywords": [
6
- "cli",
7
- "ai",
8
- "coding-assistant",
9
- "developer-tools",
10
- "terminal",
11
- "windows",
12
- "powershell"
13
- ],
14
- "type": "module",
15
- "homepage": "https://github.com/havingautism/Codemini-CLI#readme",
16
- "bugs": {
17
- "url": "https://github.com/havingautism/Codemini-CLI/issues"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/havingautism/Codemini-CLI.git"
22
- },
23
- "bin": {
24
- "codemini": "bin/coder.js",
25
- "coder": "bin/coder.js"
26
- },
27
- "scripts": {
28
- "start": "node bin/coder.js",
29
- "test": "node --test tests/*.test.js",
30
- "build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
31
- "prepack": "npm run build:web",
32
- "pack:offline": "npm pack",
33
- "bump:patch": "npm version patch --no-git-tag-version",
34
- "bump:minor": "npm version minor --no-git-tag-version",
35
- "bump:major": "npm version major --no-git-tag-version"
36
- },
37
- "files": [
38
- "bin",
39
- "src",
40
- "codemini-web/server.js",
41
- "codemini-web/lib",
42
- "codemini-web/dist",
43
- "codemini-web/codemini_logo.png",
44
- "souls",
45
- "templates",
46
- "skills",
47
- "README.md",
48
- "OPERATIONS.md",
49
- "deployment.md"
50
- ],
51
- "engines": {
52
- "node": ">=22"
53
- },
54
- "publishConfig": {
55
- "access": "public"
56
- },
57
- "dependencies": {
58
- "@cursorless/tree-sitter-wasms": "^0.8.1",
59
- "cheerio": "^1.1.2",
60
- "cli-truncate": "^6.0.0",
61
- "ink": "^7.0.0",
62
- "react": "^19.2.5",
63
- "strip-ansi": "^7.2.0",
64
- "web-tree-sitter": "^0.26.8"
65
- },
66
- "license": "MIT"
67
- }
1
+ {
2
+ "name": "codemini-cli",
3
+ "version": "0.5.9",
4
+ "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
+ "keywords": [
6
+ "cli",
7
+ "ai",
8
+ "coding-assistant",
9
+ "developer-tools",
10
+ "terminal",
11
+ "windows",
12
+ "powershell"
13
+ ],
14
+ "type": "module",
15
+ "homepage": "https://github.com/havingautism/Codemini-CLI#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/havingautism/Codemini-CLI/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/havingautism/Codemini-CLI.git"
22
+ },
23
+ "bin": {
24
+ "codemini": "bin/coder.js",
25
+ "coder": "bin/coder.js"
26
+ },
27
+ "scripts": {
28
+ "start": "node bin/coder.js",
29
+ "test": "node --test tests/*.test.js",
30
+ "build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
31
+ "prepack": "npm run build:web",
32
+ "pack:offline": "npm pack",
33
+ "bump:patch": "npm version patch --no-git-tag-version",
34
+ "bump:minor": "npm version minor --no-git-tag-version",
35
+ "bump:major": "npm version major --no-git-tag-version"
36
+ },
37
+ "files": [
38
+ "bin",
39
+ "src",
40
+ "codemini-web/server.js",
41
+ "codemini-web/lib",
42
+ "codemini-web/dist",
43
+ "codemini-web/codemini_logo.png",
44
+ "souls",
45
+ "templates",
46
+ "skills",
47
+ "README.md",
48
+ "OPERATIONS.md",
49
+ "deployment.md"
50
+ ],
51
+ "engines": {
52
+ "node": ">=22"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "dependencies": {
58
+ "@cursorless/tree-sitter-wasms": "^0.8.1",
59
+ "cheerio": "^1.1.2",
60
+ "cli-truncate": "^6.0.0",
61
+ "ink": "^7.0.0",
62
+ "react": "^19.2.5",
63
+ "strip-ansi": "^7.2.0",
64
+ "web-tree-sitter": "^0.26.8"
65
+ },
66
+ "license": "MIT"
67
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "version": 1,
3
+ "skills": {
4
+ "superpowers-lite": {
5
+ "description": "Default concise workflow for coding tasks; keep context tight, choose the right route, and verify before claiming success.",
6
+ "mode": "always",
7
+ "triggers": [],
8
+ "enabled": true,
9
+ "priority": 100
10
+ },
11
+ "brainstorm": {
12
+ "description": "Use when a feature or behavior request has multiple reasonable approaches and the missing piece is user preference, tradeoff choice, or key constraint.",
13
+ "mode": "agent_requested",
14
+ "triggers": [],
15
+ "enabled": true,
16
+ "priority": 70
17
+ },
18
+ "writing-plans": {
19
+ "description": "Use when you have a clear goal or approved design for a non-trivial task, before touching code.",
20
+ "mode": "agent_requested",
21
+ "triggers": [],
22
+ "enabled": true,
23
+ "priority": 75
24
+ },
25
+ "grill-me": {
26
+ "description": "Use when the user explicitly asks to pressure-test a plan, architecture choice, PR, launch, or product idea.",
27
+ "mode": "agent_requested",
28
+ "triggers": [],
29
+ "enabled": true,
30
+ "priority": 65
31
+ },
32
+ "project-requirements": {
33
+ "description": "Create a project requirements report with repository inspection and structured output artifacts.",
34
+ "mode": "manual",
35
+ "triggers": [],
36
+ "enabled": true,
37
+ "priority": 50
38
+ }
39
+ }
40
+ }
@@ -77,9 +77,15 @@ export async function listSkillEntries({ scope = 'all', cwd = process.cwd() } =
77
77
  name: command.name,
78
78
  version: command.metadata?.version || '0.0.0',
79
79
  description: command.metadata?.description || '',
80
+ mode: command.metadata?.mode || '',
81
+ triggers: Array.isArray(command.metadata?.triggers) ? command.metadata.triggers : [],
80
82
  scope: itemScope,
81
83
  path: command.path,
82
- enabled: itemScope === 'builtin' ? true : config.skills?.enabled?.[command.name] !== false
84
+ enabled: command.metadata?.enabled === false
85
+ ? false
86
+ : itemScope === 'builtin'
87
+ ? true
88
+ : config.skills?.enabled?.[command.name] !== false
83
89
  });
84
90
  }
85
91
  return entries.sort((a, b) => `${a.scope}:${a.name}`.localeCompare(`${b.scope}:${b.name}`));
@@ -93,14 +99,19 @@ async function readSkillMeta(name, { scope = 'all', cwd = process.cwd() } = {})
93
99
  }
94
100
  const dir = path.dirname(found.path);
95
101
  const manifestPath = path.join(dir, 'manifest.json');
102
+ const catalogPath = path.join(path.dirname(dir), 'codemini.skills.json');
96
103
  let manifest = null;
97
104
  try {
98
- manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
105
+ const catalog = JSON.parse(await fs.readFile(catalogPath, 'utf8'));
106
+ manifest = catalog?.skills?.[found.name] || null;
99
107
  } catch {
100
- manifest = null;
108
+ try {
109
+ manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
110
+ } catch {
111
+ manifest = null;
112
+ }
101
113
  }
102
- const entryFile = manifest?.entry || 'SKILL.md';
103
- const skillPath = found.path || path.join(dir, entryFile);
114
+ const skillPath = found.path || path.join(dir, 'SKILL.md');
104
115
  try {
105
116
  const content = await fs.readFile(skillPath, 'utf8');
106
117
  const firstLines = content.split('\n').slice(0, 20).join('\n');
@@ -136,6 +136,7 @@ function getCompletionCopy(language = 'zh') {
136
136
  'soul.preset': 'soul 预设',
137
137
  'soul.custom_path': '自定义 soul 路径',
138
138
  'policy.safe_mode': '安全模式开关',
139
+ 'policy.allowed_paths': '安全模式目录白名单',
139
140
  'policy.allow_dangerous_commands': '危险命令开关'
140
141
  },
141
142
  optionHints: {
@@ -145,6 +146,7 @@ function getCompletionCopy(language = 'zh') {
145
146
  'execution.mode': '可选:auto | normal | plan',
146
147
  'shell.default': '常用:bash | powershell',
147
148
  'policy.safe_mode': '可选:true | false',
149
+ 'policy.allowed_paths': 'JSON 数组,例如 ["D:\\\\shared"]',
148
150
  'policy.allow_dangerous_commands': '可选:true | false',
149
151
  'context.prompt_budget_audit': '可选:true | false'
150
152
  },
@@ -243,6 +245,7 @@ function getCompletionCopy(language = 'zh') {
243
245
  'soul.preset': 'soul preset',
244
246
  'soul.custom_path': 'custom soul prompt path',
245
247
  'policy.safe_mode': 'safe mode switch',
248
+ 'policy.allowed_paths': 'safe-mode allowed path roots',
246
249
  'policy.allow_dangerous_commands': 'dangerous command allowance'
247
250
  },
248
251
  optionHints: {
@@ -252,6 +255,7 @@ function getCompletionCopy(language = 'zh') {
252
255
  'execution.mode': 'options: auto | normal | plan',
253
256
  'shell.default': 'common: bash | powershell',
254
257
  'policy.safe_mode': 'options: true | false',
258
+ 'policy.allowed_paths': 'JSON array, for example ["D:\\\\shared"]',
255
259
  'policy.allow_dangerous_commands': 'options: true | false',
256
260
  'context.prompt_budget_audit': 'options: true | false'
257
261
  },
@@ -1265,6 +1269,7 @@ function isBundledSkillCommand(command) {
1265
1269
  }
1266
1270
 
1267
1271
  function isSkillEnabled(config, name, command = null) {
1272
+ if (command?.metadata?.enabled === false) return false;
1268
1273
  if (isBundledSkillCommand(command)) return true;
1269
1274
  return config.skills?.enabled?.[name] !== false;
1270
1275
  }
@@ -2301,6 +2306,9 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
2301
2306
  maxContextTokens,
2302
2307
  pendingPlanApproval: planState?.status === 'pending_approval'
2303
2308
  ? { goal: planState.goal, summary: planState.finalSummary || planState.summary, filePath: planState.filePath, steps: planState.steps || [] }
2309
+ : null,
2310
+ pendingReflectSkill: planState?.status === 'pending_reflect_skill'
2311
+ ? buildPendingReflectSkillSnapshot(planState)
2304
2312
  : null
2305
2313
  };
2306
2314
  Object.defineProperties(snapshot, {
@@ -2314,11 +2322,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
2314
2322
  enumerable: false,
2315
2323
  writable: false
2316
2324
  },
2317
- pendingReflectSkill: {
2318
- value: currentSession?.planState?.status === 'pending_reflect_skill',
2319
- enumerable: false,
2320
- writable: false
2321
- },
2322
2325
  replyLanguage: {
2323
2326
  value: getReplyLanguage(config),
2324
2327
  enumerable: false,
@@ -2329,7 +2332,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
2329
2332
  ...snapshot,
2330
2333
  currentContextTokens,
2331
2334
  contextUsagePct,
2332
- pendingReflectSkill: currentSession?.planState?.status === 'pending_reflect_skill',
2333
2335
  replyLanguage: getReplyLanguage(config)
2334
2336
  }),
2335
2337
  enumerable: false,
@@ -2513,6 +2515,21 @@ function buildPendingReflectSkillMessage(reflectState) {
2513
2515
  return lines.join('\n');
2514
2516
  }
2515
2517
 
2518
+ function buildPendingReflectSkillSnapshot(reflectState) {
2519
+ const candidates = Array.isArray(reflectState?.candidates) ? reflectState.candidates : [];
2520
+ const candidate = candidates[0] || null;
2521
+ if (!candidate) return null;
2522
+ return {
2523
+ scope: reflectState?.targetScope || 'project',
2524
+ request: reflectState?.request || '',
2525
+ name: candidate.name || '',
2526
+ description: candidate.description || '',
2527
+ confidence: Number(candidate.confidence ?? 0.75),
2528
+ targetPath: candidate.targetPath || '',
2529
+ content: candidate.content || ''
2530
+ };
2531
+ }
2532
+
2516
2533
  function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
2517
2534
  const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
2518
2535
  const lines = [
@@ -2740,8 +2757,10 @@ async function askModel({
2740
2757
  }
2741
2758
  }
2742
2759
 
2760
+ const shouldGenerateTitle = text
2761
+ ? !session.messages.some((msg) => msg?.role === 'user')
2762
+ : false;
2743
2763
  if (text) {
2744
- const shouldGenerateTitle = !session.messages.some((msg) => msg?.role === 'user');
2745
2764
  const modelExtra =
2746
2765
  typeof modelText === 'string' && modelText && modelText !== text ? { model_content: modelText } : {};
2747
2766
  const userMessage = stampedMessage('user', text, modelExtra);
@@ -2773,7 +2792,16 @@ async function askModel({
2773
2792
 
2774
2793
  const { definitions, handlers, formatters, deferredDefinitions, dispose: disposeTools } = getBuiltinTools({
2775
2794
  workspaceRoot: process.cwd(),
2776
- config,
2795
+ config: {
2796
+ ...config,
2797
+ policy: {
2798
+ ...(config.policy || {}),
2799
+ allowed_paths: [
2800
+ ...(Array.isArray(config.policy?.allowed_paths) ? config.policy.allowed_paths : []),
2801
+ path.join(getSessionsDir(), String(session.id))
2802
+ ]
2803
+ }
2804
+ },
2777
2805
  sessionId: session.id,
2778
2806
  onSystemEvent: onAgentEvent,
2779
2807
  getTodos: () => normalizeTodos(session.todos),
@@ -3004,7 +3032,7 @@ async function askModel({
3004
3032
  await flushScheduledSave();
3005
3033
  await saveSession(session);
3006
3034
  // Generate a better title asynchronously after saving
3007
- if (shouldReplaceSessionTitle(session.title)) {
3035
+ if (shouldGenerateTitle) {
3008
3036
  const titleSessionId = session.id;
3009
3037
  generateSessionTitle({
3010
3038
  userText: text,
@@ -4242,6 +4270,32 @@ export async function createChatRuntime({
4242
4270
  if (hasPendingPlanApproval(currentSession)) {
4243
4271
  executionMode = 'plan';
4244
4272
  }
4273
+ let compactState = null;
4274
+ const normalizeCompactThreshold = (value, fallback = 60) => {
4275
+ const num = Number(value);
4276
+ if (!Number.isFinite(num)) return fallback;
4277
+ return Math.min(95, Math.max(50, num));
4278
+ };
4279
+ const syncCompactStateFromConfig = () => {
4280
+ if (!compactState) return;
4281
+ compactState.threshold = normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60);
4282
+ };
4283
+ const syncRuntimeFromConfig = async ({ model: nextModel } = {}) => {
4284
+ const configuredMode = String(config.execution?.mode || 'normal');
4285
+ executionMode = hasPendingPlanApproval(currentSession)
4286
+ ? 'plan'
4287
+ : (['normal', 'auto', 'plan'].includes(configuredMode) ? configuredMode : 'normal');
4288
+ syncCompactStateFromConfig();
4289
+
4290
+ const resolvedModel = String(nextModel || '').trim();
4291
+ if (resolvedModel) {
4292
+ model = resolvedModel;
4293
+ if (currentSession && typeof currentSession === 'object') {
4294
+ currentSession.model = model;
4295
+ await saveSession(currentSession).catch(() => {});
4296
+ }
4297
+ }
4298
+ };
4245
4299
  const commands = await loadCommandsAndSkills();
4246
4300
  const reloadCommandsAndSkills = async () => {
4247
4301
  const next = await loadCommandsAndSkills();
@@ -4254,10 +4308,10 @@ export async function createChatRuntime({
4254
4308
  // Set up tool result store under session directory
4255
4309
  const sessionResultsDir = path.join(getSessionsDir(), String(currentSession.id));
4256
4310
  setResultDir(sessionResultsDir);
4257
- const compactState = {
4311
+ compactState = {
4258
4312
  backupMessages: null,
4259
4313
  autoEnabled: true,
4260
- threshold: 60,
4314
+ threshold: normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60),
4261
4315
  mode: 'conservative'
4262
4316
  };
4263
4317
  let compactedForModel = currentSession.compact?.view || null;
@@ -4341,6 +4395,7 @@ export async function createChatRuntime({
4341
4395
  'soul.preset',
4342
4396
  'soul.custom_path',
4343
4397
  'policy.safe_mode',
4398
+ 'policy.allowed_paths',
4344
4399
  'policy.allow_dangerous_commands'
4345
4400
  ];
4346
4401
 
@@ -4762,6 +4817,11 @@ export async function createChatRuntime({
4762
4817
  };
4763
4818
 
4764
4819
  const persistAssistantExchange = async (userText, assistantText, { includeUser = true, extra = {} } = {}) => {
4820
+ const priorUserCount = currentSession.messages.filter((msg) => msg?.role === 'user').length;
4821
+ const priorAssistantCount = currentSession.messages.filter((msg) => msg?.role === 'assistant').length;
4822
+ const shouldGenerateTitle =
4823
+ (includeUser && userText && priorUserCount === 0) ||
4824
+ (!includeUser && userText && priorUserCount === 1 && priorAssistantCount === 0);
4765
4825
  if (includeUser && userText) {
4766
4826
  appendSessionMessage(stampedMessage('user', userText));
4767
4827
  }
@@ -4772,7 +4832,7 @@ export async function createChatRuntime({
4772
4832
  currentSession.mode = executionMode || config.execution?.mode || 'normal';
4773
4833
  await saveSession(currentSession);
4774
4834
  // Generate a better title asynchronously after saving
4775
- if (shouldReplaceSessionTitle(currentSession.title)) {
4835
+ if (shouldGenerateTitle || shouldReplaceSessionTitle(currentSession.title)) {
4776
4836
  const titleSessionId = currentSession.id;
4777
4837
  generateSessionTitle({
4778
4838
  userText,
@@ -5100,6 +5160,7 @@ export async function createChatRuntime({
5100
5160
  });
5101
5161
  currentSession.planState = null;
5102
5162
  executionMode = 'auto';
5163
+ if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
5103
5164
  await reloadCommandsAndSkills();
5104
5165
  const text = `Reflect skill written and loaded: /${written.draft.name}\nPath: ${written.filePath}`;
5105
5166
  await persistLocalExchange(line, text, { includeUser: false });
@@ -5157,6 +5218,12 @@ export async function createChatRuntime({
5157
5218
  workspaceRoot: process.cwd()
5158
5219
  })
5159
5220
  };
5221
+ if (onAgentEvent) {
5222
+ onAgentEvent({
5223
+ type: 'reflect:pending_approval',
5224
+ draft: buildPendingReflectSkillSnapshot(currentSession.planState)
5225
+ });
5226
+ }
5160
5227
  const text = `Reflect skill draft revised.\n${buildPendingReflectSkillMessage(currentSession.planState)}`;
5161
5228
  await persistLocalExchange(line, text);
5162
5229
  return { type: 'system', text };
@@ -5194,6 +5261,7 @@ export async function createChatRuntime({
5194
5261
  if (hasPendingReflectSkill(currentSession)) {
5195
5262
  currentSession.planState = null;
5196
5263
  executionMode = 'auto';
5264
+ if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
5197
5265
  const text = 'Reflect skill draft discarded.';
5198
5266
  await persistLocalExchange(line, text, { includeUser: false });
5199
5267
  return { type: 'system', text };
@@ -5653,6 +5721,12 @@ export async function createChatRuntime({
5653
5721
  request: parsedReflect.request,
5654
5722
  candidates
5655
5723
  };
5724
+ if (onAgentEvent) {
5725
+ onAgentEvent({
5726
+ type: 'reflect:pending_approval',
5727
+ draft: buildPendingReflectSkillSnapshot(currentSession.planState)
5728
+ });
5729
+ }
5656
5730
  const text = buildPendingReflectSkillMessage(currentSession.planState);
5657
5731
  await persistLocalExchange(line, text);
5658
5732
  return { type: 'system', text };
@@ -5708,6 +5782,9 @@ export async function createChatRuntime({
5708
5782
  if (!key || !value) return { type: 'system', text: 'Usage: /config set <key> <value>' };
5709
5783
  await setConfigValue(key, value);
5710
5784
  config = await loadConfig();
5785
+ await syncRuntimeFromConfig(
5786
+ key === 'model.name' ? { model: config.model?.name } : {}
5787
+ );
5711
5788
  const text = `Set ${key}=${value}`;
5712
5789
  await persistLocalExchange(line, text);
5713
5790
  return { type: 'system', text };
@@ -5716,7 +5793,8 @@ export async function createChatRuntime({
5716
5793
  if (sub === 'reset') {
5717
5794
  await resetConfig();
5718
5795
  config = await loadConfig();
5719
- compactState.threshold = 60;
5796
+ await syncRuntimeFromConfig({ model: resolveDefaultModel(config) });
5797
+ syncCompactStateFromConfig();
5720
5798
  compactState.mode = 'conservative';
5721
5799
  compactState.autoEnabled = true;
5722
5800
  const text = 'Config reset complete';
@@ -6135,8 +6213,9 @@ export async function createChatRuntime({
6135
6213
  getCurrentSessionId: () => currentSession.id,
6136
6214
  getSessionMessages: () => currentSession.messages || [],
6137
6215
  getSessionCompact: () => currentSession.compact || null,
6138
- reloadConfig: async () => {
6216
+ reloadConfig: async (options = {}) => {
6139
6217
  config = await loadConfig();
6218
+ await syncRuntimeFromConfig(options);
6140
6219
  return config;
6141
6220
  },
6142
6221
  setExecutionMode: async (next) => {