codemini-cli 0.1.19 → 0.2.1
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 +44 -20
- package/package.json +4 -2
- package/src/cli.js +1 -1
- package/src/commands/chat.js +1 -0
- package/src/core/agent-loop.js +7 -2
- package/src/core/ast.js +310 -0
- package/src/core/chat-runtime.js +47 -15
- package/src/core/checkpoint-store.js +2 -1
- package/src/core/command-loader.js +3 -4
- package/src/core/config-store.js +6 -3
- package/src/core/default-system-prompt.js +1 -1
- package/src/core/paths.js +52 -58
- package/src/core/project-index.js +510 -0
- package/src/core/provider/openai-compatible.js +9 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/shell.js +122 -2
- package/src/core/task-store.js +3 -2
- package/src/core/tools.js +188 -9
- package/src/tui/chat-app.js +694 -47
package/src/core/chat-runtime.js
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
} from './context-compact.js';
|
|
29
29
|
import { buildSystemPromptWithReplyLanguage } from './reply-language.js';
|
|
30
30
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
31
|
+
import { getProjectPlansDir, getProjectSpecsDir, getProjectWorkspaceDir } from './paths.js';
|
|
32
|
+
import { buildProjectContextSnippet, initializeProjectIndex } from './project-index.js';
|
|
31
33
|
|
|
32
34
|
function toOpenAIMessages(sessionMessages) {
|
|
33
35
|
const mapped = [];
|
|
@@ -875,10 +877,13 @@ async function buildAutoPlanFinalSummary({
|
|
|
875
877
|
}
|
|
876
878
|
}
|
|
877
879
|
|
|
878
|
-
async function
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
880
|
+
async function writeMarkdownInProjectDir(subDir, title, body, fallbackName, sessionId) {
|
|
881
|
+
const dir =
|
|
882
|
+
subDir === 'specs'
|
|
883
|
+
? getProjectSpecsDir(process.cwd(), sessionId)
|
|
884
|
+
: subDir === 'plans'
|
|
885
|
+
? getProjectPlansDir(process.cwd(), sessionId)
|
|
886
|
+
: path.join(getProjectWorkspaceDir(process.cwd()), subDir, ...(sessionId ? [String(sessionId)] : []));
|
|
882
887
|
await fs.mkdir(dir, { recursive: true });
|
|
883
888
|
const slug = slugify(title).slice(0, 64);
|
|
884
889
|
const fileName = `${nowStamp()}-${slug || fallbackName}.md`;
|
|
@@ -1038,7 +1043,7 @@ async function collectLikelyImplementationFiles(cwd) {
|
|
|
1038
1043
|
return;
|
|
1039
1044
|
}
|
|
1040
1045
|
for (const entry of entries) {
|
|
1041
|
-
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.
|
|
1046
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.codemini') continue;
|
|
1042
1047
|
const abs = path.join(dir, entry.name);
|
|
1043
1048
|
if (entry.isDirectory()) {
|
|
1044
1049
|
await visit(abs);
|
|
@@ -1162,8 +1167,8 @@ function stampedMessage(role, content, extra = {}) {
|
|
|
1162
1167
|
async function resolveSpecPath(rawArg = '', sessionId = '') {
|
|
1163
1168
|
const input = String(rawArg || '').trim();
|
|
1164
1169
|
const roots = [
|
|
1165
|
-
|
|
1166
|
-
|
|
1170
|
+
getProjectSpecsDir(process.cwd(), String(sessionId || '')),
|
|
1171
|
+
getProjectSpecsDir(process.cwd())
|
|
1167
1172
|
];
|
|
1168
1173
|
|
|
1169
1174
|
if (input) {
|
|
@@ -1304,10 +1309,16 @@ async function askModel({
|
|
|
1304
1309
|
await saveSession(session);
|
|
1305
1310
|
}
|
|
1306
1311
|
|
|
1312
|
+
const projectContextSnippet = await buildProjectContextSnippet(process.cwd(), text).catch(() => '');
|
|
1313
|
+
const effectiveSystemPrompt = projectContextSnippet
|
|
1314
|
+
? `${systemPrompt}\n\n${projectContextSnippet}\n\nUse this project context as lightweight guidance. Prefer tools for fresh verification before assuming details.`
|
|
1315
|
+
: systemPrompt;
|
|
1316
|
+
|
|
1307
1317
|
const { definitions, handlers } = getBuiltinTools({
|
|
1308
1318
|
workspaceRoot: process.cwd(),
|
|
1309
1319
|
config,
|
|
1310
|
-
sessionId: session.id
|
|
1320
|
+
sessionId: session.id,
|
|
1321
|
+
onSystemEvent: onAgentEvent
|
|
1311
1322
|
});
|
|
1312
1323
|
|
|
1313
1324
|
let activeAssistantIndex = -1;
|
|
@@ -1353,7 +1364,7 @@ async function askModel({
|
|
|
1353
1364
|
|
|
1354
1365
|
const loopUserPrompt = persistSession ? '' : text;
|
|
1355
1366
|
const loopResult = await runAgentLoop({
|
|
1356
|
-
systemPrompt,
|
|
1367
|
+
systemPrompt: effectiveSystemPrompt,
|
|
1357
1368
|
userPrompt: loopUserPrompt,
|
|
1358
1369
|
model: model || config.model.name,
|
|
1359
1370
|
maxSteps: Number(config.execution?.max_steps || 16),
|
|
@@ -1377,6 +1388,9 @@ async function askModel({
|
|
|
1377
1388
|
maxRetries: config.gateway.max_retries ?? 2,
|
|
1378
1389
|
onTextDelta: (delta) => {
|
|
1379
1390
|
if (onAgentEvent) onAgentEvent({ type: 'assistant:delta', text: delta });
|
|
1391
|
+
},
|
|
1392
|
+
onToolCallDelta: (toolCall) => {
|
|
1393
|
+
if (onAgentEvent) onAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
|
|
1380
1394
|
}
|
|
1381
1395
|
});
|
|
1382
1396
|
}
|
|
@@ -1648,7 +1662,7 @@ async function buildAutoPlanAndRun({
|
|
|
1648
1662
|
lines.push('');
|
|
1649
1663
|
});
|
|
1650
1664
|
|
|
1651
|
-
const filePath = await
|
|
1665
|
+
const filePath = await writeMarkdownInProjectDir(
|
|
1652
1666
|
'plans',
|
|
1653
1667
|
`${goal}-auto`,
|
|
1654
1668
|
lines.join('\n'),
|
|
@@ -1698,6 +1712,16 @@ export async function createChatRuntime({
|
|
|
1698
1712
|
model,
|
|
1699
1713
|
systemPrompt
|
|
1700
1714
|
}) {
|
|
1715
|
+
const startupEvents = [];
|
|
1716
|
+
const initialIndex = await initializeProjectIndex(process.cwd()).catch(() => null);
|
|
1717
|
+
if (initialIndex?.summary) {
|
|
1718
|
+
startupEvents.push({
|
|
1719
|
+
type: 'system_tool',
|
|
1720
|
+
name: 'project_index(.codemini-project/project-map.json,.codemini-project/file-index.json)',
|
|
1721
|
+
status: 'done',
|
|
1722
|
+
summary: initialIndex.summary
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1701
1725
|
let currentSession = session;
|
|
1702
1726
|
let config = initialConfig;
|
|
1703
1727
|
const baseSystemPrompt = systemPrompt;
|
|
@@ -1776,8 +1800,8 @@ export async function createChatRuntime({
|
|
|
1776
1800
|
{ name: 'compact', description: 'compress message context' },
|
|
1777
1801
|
{ name: 'tasks', description: 'task board management' },
|
|
1778
1802
|
{ name: 'checkpoint', description: 'create/list/load conversation checkpoints' },
|
|
1779
|
-
{ name: 'spec', description: 'create a spec markdown file in .
|
|
1780
|
-
{ name: 'plan', description: 'create an implementation plan markdown file in .
|
|
1803
|
+
{ name: 'spec', description: 'create a spec markdown file in .codemini/specs' },
|
|
1804
|
+
{ name: 'plan', description: 'create an implementation plan markdown file in .codemini/plans' },
|
|
1781
1805
|
{ name: 'agents', description: 'run/list sub-agent roles' },
|
|
1782
1806
|
{ name: 'config', description: 'set/get/list/reset config values' },
|
|
1783
1807
|
{ name: 'history', description: 'list/resume sessions' },
|
|
@@ -2317,7 +2341,7 @@ export async function createChatRuntime({
|
|
|
2317
2341
|
content = buildSpecTemplate(topic);
|
|
2318
2342
|
buildNote = `\nGenerated with fallback template because model spec generation failed: ${String(err?.message || err)}`;
|
|
2319
2343
|
}
|
|
2320
|
-
const filePath = await
|
|
2344
|
+
const filePath = await writeMarkdownInProjectDir(
|
|
2321
2345
|
'specs',
|
|
2322
2346
|
topic,
|
|
2323
2347
|
content,
|
|
@@ -2371,7 +2395,7 @@ export async function createChatRuntime({
|
|
|
2371
2395
|
planContent = buildPlanTemplate(specTitle);
|
|
2372
2396
|
buildNote = `\nGenerated with fallback template because model plan generation failed: ${String(err?.message || err)}`;
|
|
2373
2397
|
}
|
|
2374
|
-
const filePath = await
|
|
2398
|
+
const filePath = await writeMarkdownInProjectDir(
|
|
2375
2399
|
'plans',
|
|
2376
2400
|
`${specTitle}-from-spec`,
|
|
2377
2401
|
planContent,
|
|
@@ -2386,7 +2410,7 @@ export async function createChatRuntime({
|
|
|
2386
2410
|
const goal = parsedInput.args.join(' ').trim();
|
|
2387
2411
|
if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan from-spec <spec-path?>' };
|
|
2388
2412
|
const content = buildPlanTemplate(goal);
|
|
2389
|
-
const filePath = await
|
|
2413
|
+
const filePath = await writeMarkdownInProjectDir(
|
|
2390
2414
|
'plans',
|
|
2391
2415
|
goal,
|
|
2392
2416
|
content,
|
|
@@ -2672,6 +2696,13 @@ export async function createChatRuntime({
|
|
|
2672
2696
|
}
|
|
2673
2697
|
|
|
2674
2698
|
const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
|
|
2699
|
+
const selectedAutoSkills = selectAutoSkillNames(expandedText).filter((name) => isSkillEnabled(config, name));
|
|
2700
|
+
if (selectedAutoSkills.length > 0 && onAgentEvent) {
|
|
2701
|
+
onAgentEvent({
|
|
2702
|
+
type: 'skill:auto',
|
|
2703
|
+
names: selectedAutoSkills
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2675
2706
|
const routedSystemPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
|
|
2676
2707
|
const result = await askModel({
|
|
2677
2708
|
text: expandedText,
|
|
@@ -2690,6 +2721,7 @@ export async function createChatRuntime({
|
|
|
2690
2721
|
getCompletionOptions,
|
|
2691
2722
|
isImmediateLocalInput,
|
|
2692
2723
|
submit,
|
|
2724
|
+
consumeStartupEvents: () => startupEvents.splice(0, startupEvents.length),
|
|
2693
2725
|
getInputHistory: () => loadInputHistory(),
|
|
2694
2726
|
getCurrentSessionId: () => currentSession.id,
|
|
2695
2727
|
getRuntimeState: () =>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { getProjectCheckpointsDir } from './paths.js';
|
|
3
4
|
|
|
4
5
|
function checkpointsDir(cwd = process.cwd()) {
|
|
5
|
-
return
|
|
6
|
+
return getProjectCheckpointsDir(cwd);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
function makeId(name = '') {
|
|
@@ -3,9 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import {
|
|
5
5
|
getCommandsDir,
|
|
6
|
-
getLegacyGlobalSkillsDir,
|
|
7
|
-
getLegacyProjectSkillsDir,
|
|
8
6
|
getProjectCommandsDir,
|
|
7
|
+
getProjectSkillsDir,
|
|
9
8
|
getSkillsDir
|
|
10
9
|
} from './paths.js';
|
|
11
10
|
import { readSkillRegistry } from './skill-registry.js';
|
|
@@ -188,8 +187,8 @@ export async function loadCommandsAndSkills(cwd = process.cwd()) {
|
|
|
188
187
|
loadBundledSkillsFromDir(BUNDLED_SKILLS_DIR, commands);
|
|
189
188
|
loadMarkdownCommandsFromDir(getCommandsDir(), 'global', commands);
|
|
190
189
|
loadMarkdownCommandsFromDir(getProjectCommandsDir(cwd), 'project', commands);
|
|
191
|
-
loadLegacySkillsFromDir(
|
|
192
|
-
loadLegacySkillsFromDir(
|
|
190
|
+
loadLegacySkillsFromDir(getSkillsDir(), 'global', commands);
|
|
191
|
+
loadLegacySkillsFromDir(getProjectSkillsDir(cwd), 'project', commands);
|
|
193
192
|
const registry = await readSkillRegistry();
|
|
194
193
|
loadInstalledSkillsFromRegistry(getSkillsDir(), registry, commands);
|
|
195
194
|
|
package/src/core/config-store.js
CHANGED
|
@@ -193,8 +193,10 @@ export async function loadConfig() {
|
|
|
193
193
|
const parsed = JSON.parse(raw);
|
|
194
194
|
return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
|
|
195
195
|
} catch {
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
const defaultConfig = normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
|
|
197
|
+
if (process.env.CODEMINI_GLOBAL_DIR) {
|
|
198
|
+
await saveConfig(defaultConfig);
|
|
199
|
+
return defaultConfig;
|
|
198
200
|
}
|
|
199
201
|
try {
|
|
200
202
|
const legacyPath = path.join(getLegacyConfigDir(), 'config.json');
|
|
@@ -202,7 +204,8 @@ export async function loadConfig() {
|
|
|
202
204
|
const parsed = JSON.parse(raw);
|
|
203
205
|
return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
|
|
204
206
|
} catch {
|
|
205
|
-
|
|
207
|
+
await saveConfig(defaultConfig);
|
|
208
|
+
return defaultConfig;
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getShellSystemPrompt } from './shell-profile.js';
|
|
2
2
|
|
|
3
3
|
export function buildDefaultSystemPrompt(config = {}) {
|
|
4
|
-
return `${getShellSystemPrompt(config?.shell?.default)} If a command or tool is blocked or fails, inspect the error and retry with allowed commands or tools. Do not claim filesystem access is impossible unless the allowed search/read tools also fail.`;
|
|
4
|
+
return `${getShellSystemPrompt(config?.shell?.default)} If a command or tool is blocked or fails, inspect the error and retry with allowed commands or tools. For AST-scoped edits, if edit rejects a call because kind=replace_block or ast_target is missing or stale, fix the tool arguments and retry instead of switching to a broader text edit. Do not claim filesystem access is impossible unless the allowed search/read tools also fail.`;
|
|
5
5
|
}
|
package/src/core/paths.js
CHANGED
|
@@ -1,75 +1,33 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const GLOBAL_APP_DIR = 'codemini-global';
|
|
5
|
+
const PROJECT_APP_DIR = '.codemini';
|
|
6
|
+
const PROJECT_INDEX_DIR = '.codemini-project';
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
if (process.env.
|
|
10
|
-
return process.env.
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (process.env.COMPANY_CODER_CONFIG_DIR) {
|
|
14
|
-
return process.env.COMPANY_CODER_CONFIG_DIR;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (process.platform === 'win32') {
|
|
18
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
19
|
-
return path.join(appData, APP_DIR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (process.platform === 'darwin') {
|
|
23
|
-
return path.join(os.homedir(), 'Library', 'Preferences', APP_DIR);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (process.env.XDG_CONFIG_HOME) {
|
|
27
|
-
return path.join(process.env.XDG_CONFIG_HOME, APP_DIR);
|
|
8
|
+
export function getBaseConfigDir() {
|
|
9
|
+
if (process.env.CODEMINI_GLOBAL_DIR) {
|
|
10
|
+
return process.env.CODEMINI_GLOBAL_DIR;
|
|
28
11
|
}
|
|
29
12
|
|
|
30
|
-
// Fallback for restricted/sandboxed non-Windows environments.
|
|
31
|
-
return path.join(process.cwd(), '.codemini-cli');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getLegacyBaseConfigDir() {
|
|
35
13
|
if (process.platform === 'win32') {
|
|
36
14
|
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
37
|
-
return path.join(appData,
|
|
15
|
+
return path.join(appData, GLOBAL_APP_DIR);
|
|
38
16
|
}
|
|
39
17
|
|
|
40
18
|
if (process.platform === 'darwin') {
|
|
41
|
-
return path.join(os.homedir(), 'Library', 'Preferences',
|
|
19
|
+
return path.join(os.homedir(), 'Library', 'Preferences', GLOBAL_APP_DIR);
|
|
42
20
|
}
|
|
43
21
|
|
|
44
22
|
if (process.env.XDG_CONFIG_HOME) {
|
|
45
|
-
return path.join(process.env.XDG_CONFIG_HOME,
|
|
23
|
+
return path.join(process.env.XDG_CONFIG_HOME, GLOBAL_APP_DIR);
|
|
46
24
|
}
|
|
47
25
|
|
|
48
|
-
return path.join(process.cwd(), '.
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function tryMigrateLegacyDir(preferred, legacy) {
|
|
52
|
-
if (!preferred || !legacy || preferred === legacy) return preferred;
|
|
53
|
-
if (fs.existsSync(preferred) || !fs.existsSync(legacy)) return preferred;
|
|
54
|
-
try {
|
|
55
|
-
fs.renameSync(legacy, preferred);
|
|
56
|
-
return preferred;
|
|
57
|
-
} catch {
|
|
58
|
-
return preferred;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getBaseConfigDir() {
|
|
63
|
-
const preferred = getPreferredBaseConfigDir();
|
|
64
|
-
if (process.env.CODEMINI_CONFIG_DIR || process.env.COMPANY_CODER_CONFIG_DIR) {
|
|
65
|
-
return preferred;
|
|
66
|
-
}
|
|
67
|
-
const legacy = getLegacyBaseConfigDir();
|
|
68
|
-
return tryMigrateLegacyDir(preferred, legacy);
|
|
26
|
+
return path.join(process.cwd(), '.codemini-global');
|
|
69
27
|
}
|
|
70
28
|
|
|
71
29
|
export function getLegacyConfigDir() {
|
|
72
|
-
return
|
|
30
|
+
return getBaseConfigDir();
|
|
73
31
|
}
|
|
74
32
|
|
|
75
33
|
export function getConfigFilePath() {
|
|
@@ -96,14 +54,50 @@ export function getInputHistoryFilePath() {
|
|
|
96
54
|
return path.join(getBaseConfigDir(), 'input-history.json');
|
|
97
55
|
}
|
|
98
56
|
|
|
57
|
+
export function getProjectWorkspaceDir(cwd = process.cwd()) {
|
|
58
|
+
return path.join(cwd, PROJECT_APP_DIR);
|
|
59
|
+
}
|
|
60
|
+
|
|
99
61
|
export function getProjectCommandsDir(cwd = process.cwd()) {
|
|
100
|
-
return path.join(cwd, '
|
|
62
|
+
return path.join(getProjectWorkspaceDir(cwd), 'commands');
|
|
101
63
|
}
|
|
102
64
|
|
|
103
|
-
export function
|
|
104
|
-
return path.join(cwd, '
|
|
65
|
+
export function getProjectSkillsDir(cwd = process.cwd()) {
|
|
66
|
+
return path.join(getProjectWorkspaceDir(cwd), 'skills');
|
|
105
67
|
}
|
|
106
68
|
|
|
107
|
-
export function
|
|
108
|
-
return
|
|
69
|
+
export function getProjectSpecsDir(cwd = process.cwd(), sessionId = '') {
|
|
70
|
+
return sessionId
|
|
71
|
+
? path.join(getProjectWorkspaceDir(cwd), 'specs', String(sessionId))
|
|
72
|
+
: path.join(getProjectWorkspaceDir(cwd), 'specs');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getProjectPlansDir(cwd = process.cwd(), sessionId = '') {
|
|
76
|
+
return sessionId
|
|
77
|
+
? path.join(getProjectWorkspaceDir(cwd), 'plans', String(sessionId))
|
|
78
|
+
: path.join(getProjectWorkspaceDir(cwd), 'plans');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getProjectCheckpointsDir(cwd = process.cwd()) {
|
|
82
|
+
return path.join(getProjectWorkspaceDir(cwd), 'checkpoints');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getProjectTasksDir(cwd = process.cwd()) {
|
|
86
|
+
return path.join(getProjectWorkspaceDir(cwd), 'tasks');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getProjectLegacyTasksFilePath(cwd = process.cwd()) {
|
|
90
|
+
return path.join(getProjectWorkspaceDir(cwd), 'tasks.json');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getProjectMapPath(cwd = process.cwd()) {
|
|
94
|
+
return path.join(cwd, PROJECT_INDEX_DIR, 'project-map.json');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getFileIndexPath(cwd = process.cwd()) {
|
|
98
|
+
return path.join(cwd, PROJECT_INDEX_DIR, 'file-index.json');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getProjectIndexDir(cwd = process.cwd()) {
|
|
102
|
+
return path.join(cwd, PROJECT_INDEX_DIR);
|
|
109
103
|
}
|